diff --git a/src/proto_alpha/bin_sc_rollup_node/daemon.ml b/src/proto_alpha/bin_sc_rollup_node/daemon.ml index 01fdf564606c32ae0c26dc16fafd72bce7d7608c..db67c833c2c4a9a74d2dd5fa2ad6ed336b2ef82d 100644 --- a/src/proto_alpha/bin_sc_rollup_node/daemon.ml +++ b/src/proto_alpha/bin_sc_rollup_node/daemon.ml @@ -118,7 +118,8 @@ module Make (PVM : Pvm.S) = struct | Tx_rollup_submit_batch _ | Tx_rollup_commit _ | Tx_rollup_return_bond _ | Tx_rollup_finalize_commitment _ | Tx_rollup_remove_commitment _ | Tx_rollup_rejection _ | Tx_rollup_dispatch_tickets _ | Transfer_ticket _ - | Sc_rollup_originate _ | Zk_rollup_origination _ | Zk_rollup_publish _ -> + | Sc_rollup_originate _ | Zk_rollup_origination _ | Zk_rollup_publish _ + | Zk_rollup_update _ -> false in if not (is_for_my_rollup operation) then return_unit diff --git a/src/proto_alpha/bin_sc_rollup_node/injector.ml b/src/proto_alpha/bin_sc_rollup_node/injector.ml index 625221487cbc7e8b2be07537511abee76a1ba715..556ffbbc8fc78a4c6febee96ac96e50345c3cb31 100644 --- a/src/proto_alpha/bin_sc_rollup_node/injector.ml +++ b/src/proto_alpha/bin_sc_rollup_node/injector.ml @@ -157,7 +157,7 @@ module Parameters : | Transfer_ticket _ | Dal_publish_slot_header _ | Sc_rollup_originate _ | Sc_rollup_execute_outbox_message _ | Sc_rollup_recover_bond _ | Zk_rollup_origination _ - | Zk_rollup_publish _ -> + | Zk_rollup_publish _ | Zk_rollup_update _ -> (* These operations should never be handled by this injector *) assert false) diff --git a/src/proto_alpha/lib_client/injection.ml b/src/proto_alpha/lib_client/injection.ml index c29f8c69a6ff22caf8a8e802b30c73a6a35b1fb4..b7b8f98fdb92fb97fbc347ca7b631323b5f9c36d 100644 --- a/src/proto_alpha/lib_client/injection.ml +++ b/src/proto_alpha/lib_client/injection.ml @@ -339,7 +339,8 @@ let estimated_gas_single (type kind) | Sc_rollup_recover_bond_result {consumed_gas; _} -> Ok consumed_gas | Zk_rollup_origination_result {consumed_gas; _} -> Ok consumed_gas - | Zk_rollup_publish_result {consumed_gas; _} -> Ok consumed_gas) + | Zk_rollup_publish_result {consumed_gas; _} -> Ok consumed_gas + | Zk_rollup_update_result {consumed_gas; _} -> Ok consumed_gas) | Skipped _ -> error_with "Cannot estimate gas of skipped operation" (* There must be another error for this to happen, and it should not @@ -396,6 +397,7 @@ let estimated_storage_single (type kind) ~tx_rollup_origination_size | Tx_rollup_dispatch_tickets_result {paid_storage_size_diff; _} | Transfer_ticket_result {paid_storage_size_diff; _} | Zk_rollup_publish_result {paid_storage_size_diff; _} + | Zk_rollup_update_result {paid_storage_size_diff; _} | Transaction_result (Transaction_to_zk_rollup_result {paid_storage_size_diff; _}) -> Ok paid_storage_size_diff @@ -514,7 +516,7 @@ let originated_contracts_single (type kind) | Sc_rollup_publish_result _ | Sc_rollup_refute_result _ | Sc_rollup_timeout_result _ | Sc_rollup_execute_outbox_message_result _ | Sc_rollup_recover_bond_result _ | Zk_rollup_origination_result _ - | Zk_rollup_publish_result _ -> + | Zk_rollup_publish_result _ | Zk_rollup_update_result _ -> Ok []) | Skipped _ -> error_with "Cannot know originated contracts of skipped operation" diff --git a/src/proto_alpha/lib_client/operation_result.ml b/src/proto_alpha/lib_client/operation_result.ml index e60b6f562ce066f76b2f86f4ddfcc5b657203ee0..72ca9b9a44f9dbef65e8b7abfef370cbb0539e10 100644 --- a/src/proto_alpha/lib_client/operation_result.ml +++ b/src/proto_alpha/lib_client/operation_result.ml @@ -408,6 +408,8 @@ let pp_manager_operation_content (type kind) source ppf Format.fprintf ppf "Zk rollup origination:@,From: %a" Contract.pp source | Zk_rollup_publish _ -> Format.fprintf ppf "Zk rollup publish:@,From: %a" Contract.pp source + | Zk_rollup_update _ -> + Format.fprintf ppf "Zk rollup update:@,From: %a" Contract.pp source let pp_balance_updates ppf balance_updates = let open Receipt in @@ -839,6 +841,13 @@ let pp_manager_operation_contents_result ppf op_result = pp_consumed_gas ppf consumed_gas ; pp_balance_updates ppf balance_updates in + let pp_zk_rollup_update_result + (Zk_rollup_update_result + {balance_updates; consumed_gas; paid_storage_size_diff}) = + pp_consumed_gas ppf consumed_gas ; + pp_paid_storage_size_diff ppf paid_storage_size_diff ; + pp_balance_updates ppf balance_updates + in let manager_operation_name (type kind) (result : kind successful_manager_operation_result) = @@ -878,6 +887,7 @@ let pp_manager_operation_contents_result ppf op_result = "data availability slot header publishing" | Zk_rollup_origination_result _ -> "zk rollup originate" | Zk_rollup_publish_result _ -> "zk rollup publish" + | Zk_rollup_update_result _ -> "zk rollup update" in let pp_manager_operation_contents_result (type kind) ppf (result : kind successful_manager_operation_result) = @@ -921,6 +931,7 @@ let pp_manager_operation_contents_result ppf op_result = pp_dal_publish_slot_header_result op | Zk_rollup_origination_result _ as op -> pp_zk_rollup_origination_result op | Zk_rollup_publish_result _ as op -> pp_zk_rollup_publish_result op + | Zk_rollup_update_result _ as op -> pp_zk_rollup_update_result op in pp_operation_result ~operation_name:manager_operation_name diff --git a/src/proto_alpha/lib_injector/l1_operation.ml b/src/proto_alpha/lib_injector/l1_operation.ml index 839ba22e499c49ba08505583119b437824bc1f93..bc10f74dc96d43956f0e7682074ee9b863a6fcc2 100644 --- a/src/proto_alpha/lib_injector/l1_operation.ml +++ b/src/proto_alpha/lib_injector/l1_operation.ml @@ -106,6 +106,7 @@ module Manager_operation = struct | Sc_rollup_recover_bond _ -> sc_rollup_recover_bond_case | Zk_rollup_origination _ -> zk_rollup_origination_case | Zk_rollup_publish _ -> zk_rollup_publish_case + | Zk_rollup_update _ -> zk_rollup_update_case let pp_kind ppf op = let open Operation.Encoding.Manager_operations in diff --git a/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL b/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL index 69d336c8fb180de7c8de98d94d4284065429b33e..6db5b22c0ad34f852623f4e67a630e1463d981b2 100644 --- a/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL +++ b/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL @@ -92,6 +92,8 @@ "Zk_rollup_account_repr", "Zk_rollup_ticket_repr", "Zk_rollup_operation_repr", + "Zk_rollup_update_repr", + "Zk_rollup_circuit_public_inputs_repr", "Bond_id_repr", "Vote_repr", diff --git a/src/proto_alpha/lib_protocol/alpha_context.ml b/src/proto_alpha/lib_protocol/alpha_context.ml index 6403ff9bd3cf95dfac39c966ed09295832bde693..2a392d8c8a39e4bedfbae76076d4a9bcce264b4b 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.ml +++ b/src/proto_alpha/lib_protocol/alpha_context.ml @@ -136,6 +136,8 @@ module Zk_rollup = struct module Operation = Zk_rollup_operation_repr module Ticket = Zk_rollup_ticket_repr module Errors = Zk_rollup_errors + module Circuit_public_inputs = Zk_rollup_circuit_public_inputs_repr + module Update = Zk_rollup_update_repr include Zk_rollup_storage end diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index 89e46fd4cef219fd4b9676477fc9c48fe3f819c5..aebebe920ef31184447c0ccc9ce0423936da3e10 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -2387,7 +2387,7 @@ module Zk_rollup : sig type static = { public_parameters : Plonk.public_parameters; state_length : int; - circuits_info : bool SMap.t; + circuits_info : [`Public | `Private | `Fee] SMap.t; nb_ops : int; } @@ -2425,6 +2425,54 @@ module Zk_rollup : sig val encoding : t Data_encoding.t end + module Circuit_public_inputs : sig + type pending_op_public_inputs = { + old_state : State.t; + new_state : State.t; + fee : scalar; + exit_validity : bool; + zk_rollup : t; + l2_op : Operation.t; + } + + type private_batch_public_inputs = { + old_state : State.t; + new_state : State.t; + fees : scalar; + zk_rollup : t; + } + + type fee_public_inputs = { + old_state : State.t; + new_state : State.t; + fees : scalar; + } + + type t = + | Pending_op of pending_op_public_inputs + | Private_batch of private_batch_public_inputs + | Fee of fee_public_inputs + + val to_scalar_array : t -> scalar array + end + + module Update : sig + type op_pi = {new_state : State.t; fee : scalar; exit_validity : bool} + + type private_inner_pi = {new_state : State.t; fees : scalar} + + type fee_pi = {new_state : State.t} + + type t = { + pending_pis : (string * op_pi) list; + private_pis : (string * private_inner_pi) list; + fee_pi : fee_pi; + proof : Plonk.proof; + } + + val encoding : t Data_encoding.t + end + type pending_list = | Empty of {next_index : int64} | Pending of {next_index : int64; length : int} @@ -2445,6 +2493,32 @@ module Zk_rollup : sig (Operation.t * Ticket_hash.t option) list -> (context * Z.t) tzresult Lwt.t + val get_pending_length : + context -> Address.t -> (context * int) tzresult Lwt.t + + val get_prefix : + context -> + Address.t -> + int -> + (context * (Operation.t * Ticket_hash.t option) list) tzresult Lwt.t + + val update : + context -> + Address.t -> + pending_to_drop:int -> + new_account:Account.t -> + context tzresult Lwt.t + + val account : context -> t -> (context * Account.t) tzresult Lwt.t + + val pending_list : context -> t -> (context * pending_list) tzresult Lwt.t + + val pending_op : + context -> + t -> + Int64.t -> + (context * (Operation.t * Ticket_hash.t option)) tzresult Lwt.t + val assert_exist : context -> t -> context tzresult Lwt.t val exists : context -> t -> (context * bool) tzresult Lwt.t @@ -2459,6 +2533,10 @@ module Zk_rollup : sig payload_size : Saturation_repr.may_saturate Saturation_repr.t; limit : int; } + | Invalid_verification + | Invalid_circuit + | Inconsistent_state_update + | Pending_bound end module Internal_for_tests : sig @@ -4263,6 +4341,8 @@ module Kind : sig type zk_rollup_publish = Zk_rollup_publish_kind + type zk_rollup_update = Zk_rollup_update_kind + type 'a manager = | Reveal_manager_kind : reveal manager | Transaction_manager_kind : transaction manager @@ -4297,6 +4377,7 @@ module Kind : sig | Sc_rollup_recover_bond_manager_kind : sc_rollup_recover_bond manager | Zk_rollup_origination_manager_kind : zk_rollup_origination manager | Zk_rollup_publish_manager_kind : zk_rollup_publish manager + | Zk_rollup_update_manager_kind : zk_rollup_update manager end (** All the definitions below are re-exported from {!Operation_repr}. *) @@ -4537,7 +4618,7 @@ and _ manager_operation = -> Kind.sc_rollup_recover_bond manager_operation | Zk_rollup_origination : { public_parameters : Plonk.public_parameters; - circuits_info : bool Zk_rollup.Account.SMap.t; + circuits_info : [`Public | `Private | `Fee] Zk_rollup.Account.SMap.t; init_state : Zk_rollup.State.t; nb_ops : int; } @@ -4547,6 +4628,11 @@ and _ manager_operation = ops : (Zk_rollup.Operation.t * Zk_rollup.Ticket.t option) list; } -> Kind.zk_rollup_publish manager_operation + | Zk_rollup_update : { + zk_rollup : Zk_rollup.t; + update : Zk_rollup.Update.t; + } + -> Kind.zk_rollup_update manager_operation type packed_manager_operation = | Manager : 'kind manager_operation -> packed_manager_operation @@ -4744,6 +4830,8 @@ module Operation : sig val zk_rollup_publish_case : Kind.zk_rollup_publish Kind.manager case + val zk_rollup_update_case : Kind.zk_rollup_update Kind.manager case + module Manager_operations : sig type 'b case = | MCase : { @@ -4816,6 +4904,8 @@ module Operation : sig val zk_rollup_origination_case : Kind.zk_rollup_origination case val zk_rollup_publish_case : Kind.zk_rollup_publish case + + val zk_rollup_update_case : Kind.zk_rollup_update case end end diff --git a/src/proto_alpha/lib_protocol/apply.ml b/src/proto_alpha/lib_protocol/apply.ml index fcdb6314116d6cfcacbfb96aa4c4a5e62dec1519..1478bb97d1fe5cf7b4d82c6559aa6332b242d1b7 100644 --- a/src/proto_alpha/lib_protocol/apply.ml +++ b/src/proto_alpha/lib_protocol/apply.ml @@ -1414,6 +1414,8 @@ let apply_manager_operation : ~nb_ops | Zk_rollup_publish {zk_rollup; ops} -> Zk_rollup_apply.publish ~ctxt_before_op ~ctxt ~zk_rollup ~l2_ops:ops + | Zk_rollup_update {zk_rollup; update} -> + Zk_rollup_apply.update ~ctxt_before_op ~ctxt ~zk_rollup ~update type success_or_failure = Success of context | Failure @@ -1663,6 +1665,15 @@ let burn_manager_storage_fees : ( ctxt, storage_limit, Zk_rollup_publish_result {payload with balance_updates} ) + | Zk_rollup_update_result + ({paid_storage_size_diff; balance_updates; _} as payload) -> + let consumed = paid_storage_size_diff in + Fees.burn_storage_fees ctxt ~storage_limit ~payer consumed + >|=? fun (ctxt, storage_limit, storage_bus) -> + let balance_updates = storage_bus @ balance_updates in + ( ctxt, + storage_limit, + Zk_rollup_update_result {payload with balance_updates} ) (** [burn_internal_storage_fees ctxt smopr storage_limit payer] burns the storage fees associated to an internal operation result [smopr]. diff --git a/src/proto_alpha/lib_protocol/apply_results.ml b/src/proto_alpha/lib_protocol/apply_results.ml index c50ae2115167e8f023ece4a5bc4fab6749fed56a..a4ca54357bd6c72f858e79c670468ab5140be987 100644 --- a/src/proto_alpha/lib_protocol/apply_results.ml +++ b/src/proto_alpha/lib_protocol/apply_results.ml @@ -187,6 +187,12 @@ type _ successful_manager_operation_result = paid_storage_size_diff : Z.t; } -> Kind.zk_rollup_publish successful_manager_operation_result + | Zk_rollup_update_result : { + balance_updates : Receipt.balance_updates; + consumed_gas : Gas.Arith.fp; + paid_storage_size_diff : Z.t; + } + -> Kind.zk_rollup_update successful_manager_operation_result let migration_origination_result_to_successful_manager_operation_result ({ @@ -819,6 +825,27 @@ module Manager_result = struct Zk_rollup_publish_result {balance_updates; consumed_gas; paid_storage_size_diff}) + let zk_rollup_update_case = + make + ~op_case:Operation.Encoding.Manager_operations.zk_rollup_update_case + ~encoding: + Data_encoding.( + obj3 + (req "balance_updates" Receipt.balance_updates_encoding) + (dft "consumed_milligas" Gas.Arith.n_fp_encoding Gas.Arith.zero) + (dft "paid_storage_size_diff" z Z.zero)) + ~select:(function + | Successful_manager_result (Zk_rollup_update_result _ as op) -> Some op + | _ -> None) + ~kind:Kind.Zk_rollup_update_manager_kind + ~proj:(function + | Zk_rollup_update_result + {balance_updates; consumed_gas; paid_storage_size_diff} -> + (balance_updates, consumed_gas, paid_storage_size_diff)) + ~inj:(fun (balance_updates, consumed_gas, paid_storage_size_diff) -> + Zk_rollup_update_result + {balance_updates; consumed_gas; paid_storage_size_diff}) + let sc_rollup_originate_case = make ~op_case:Operation.Encoding.Manager_operations.sc_rollup_originate_case @@ -1200,6 +1227,9 @@ let equal_manager_kind : | Kind.Zk_rollup_publish_manager_kind, Kind.Zk_rollup_publish_manager_kind -> Some Eq | Kind.Zk_rollup_publish_manager_kind, _ -> None + | Kind.Zk_rollup_update_manager_kind, Kind.Zk_rollup_update_manager_kind -> + Some Eq + | Kind.Zk_rollup_update_manager_kind, _ -> None module Encoding = struct type 'kind case = @@ -1869,6 +1899,17 @@ module Encoding = struct -> Some (op, res) | _ -> None) + + let zk_rollup_update_case = + make_manager_case + Operation.Encoding.zk_rollup_update_case + Manager_result.zk_rollup_update_case + (function + | Contents_and_result + ((Manager_operation {operation = Zk_rollup_update _; _} as op), res) + -> + Some (op, res) + | _ -> None) end let contents_result_encoding = @@ -1930,6 +1971,7 @@ let contents_result_encoding = make sc_rollup_recover_bond_case; make zk_rollup_origination_case; make zk_rollup_publish_case; + make zk_rollup_update_case; ] let contents_and_result_encoding = @@ -1996,6 +2038,7 @@ let contents_and_result_encoding = make sc_rollup_recover_bond_case; make zk_rollup_origination_case; make zk_rollup_publish_case; + make zk_rollup_update_case; ] type 'kind contents_result_list = @@ -2871,6 +2914,31 @@ let kind_equal : } ) -> Some Eq | Manager_operation {operation = Zk_rollup_publish _; _}, _ -> None + | ( Manager_operation {operation = Zk_rollup_update _; _}, + Manager_operation_result + {operation_result = Applied (Zk_rollup_update_result _); _} ) -> + Some Eq + | ( Manager_operation {operation = Zk_rollup_update _; _}, + Manager_operation_result + {operation_result = Backtracked (Zk_rollup_update_result _, _); _} ) -> + Some Eq + | ( Manager_operation {operation = Zk_rollup_update _; _}, + Manager_operation_result + { + operation_result = + Failed (Alpha_context.Kind.Zk_rollup_update_manager_kind, _); + _; + } ) -> + Some Eq + | ( Manager_operation {operation = Zk_rollup_update _; _}, + Manager_operation_result + { + operation_result = + Skipped Alpha_context.Kind.Zk_rollup_update_manager_kind; + _; + } ) -> + Some Eq + | Manager_operation {operation = Zk_rollup_update _; _}, _ -> None let rec kind_equal_list : type kind kind2. diff --git a/src/proto_alpha/lib_protocol/apply_results.mli b/src/proto_alpha/lib_protocol/apply_results.mli index 59c1f1156e66492cd7d25e9dbd2746e398dc6255..26cb1cf5ce07fe4c99f1857e9a07e935b936fcae 100644 --- a/src/proto_alpha/lib_protocol/apply_results.mli +++ b/src/proto_alpha/lib_protocol/apply_results.mli @@ -292,6 +292,12 @@ and _ successful_manager_operation_result = paid_storage_size_diff : Z.t; } -> Kind.zk_rollup_publish successful_manager_operation_result + | Zk_rollup_update_result : { + balance_updates : Receipt.balance_updates; + consumed_gas : Gas.Arith.fp; + paid_storage_size_diff : Z.t; + } + -> Kind.zk_rollup_update successful_manager_operation_result and packed_successful_manager_operation_result = | Successful_manager_result : diff --git a/src/proto_alpha/lib_protocol/dune b/src/proto_alpha/lib_protocol/dune index 2caa12aa0a9a6df1d8662aef4a3f61c564ea2618..83ad6c74bbbabb2ed4a3826b5b97d1ad7723785a 100644 --- a/src/proto_alpha/lib_protocol/dune +++ b/src/proto_alpha/lib_protocol/dune @@ -116,6 +116,8 @@ Zk_rollup_account_repr Zk_rollup_ticket_repr Zk_rollup_operation_repr + Zk_rollup_update_repr + Zk_rollup_circuit_public_inputs_repr Bond_id_repr Vote_repr Liquidity_baking_repr @@ -386,6 +388,9 @@ zk_rollup_account_repr.ml zk_rollup_account_repr.mli zk_rollup_ticket_repr.ml zk_rollup_ticket_repr.mli zk_rollup_operation_repr.ml zk_rollup_operation_repr.mli + zk_rollup_update_repr.ml zk_rollup_update_repr.mli + zk_rollup_circuit_public_inputs_repr.ml + zk_rollup_circuit_public_inputs_repr.mli bond_id_repr.ml bond_id_repr.mli vote_repr.ml vote_repr.mli liquidity_baking_repr.ml liquidity_baking_repr.mli @@ -637,6 +642,9 @@ zk_rollup_account_repr.ml zk_rollup_account_repr.mli zk_rollup_ticket_repr.ml zk_rollup_ticket_repr.mli zk_rollup_operation_repr.ml zk_rollup_operation_repr.mli + zk_rollup_update_repr.ml zk_rollup_update_repr.mli + zk_rollup_circuit_public_inputs_repr.ml + zk_rollup_circuit_public_inputs_repr.mli bond_id_repr.ml bond_id_repr.mli vote_repr.ml vote_repr.mli liquidity_baking_repr.ml liquidity_baking_repr.mli @@ -893,6 +901,9 @@ zk_rollup_account_repr.ml zk_rollup_account_repr.mli zk_rollup_ticket_repr.ml zk_rollup_ticket_repr.mli zk_rollup_operation_repr.ml zk_rollup_operation_repr.mli + zk_rollup_update_repr.ml zk_rollup_update_repr.mli + zk_rollup_circuit_public_inputs_repr.ml + zk_rollup_circuit_public_inputs_repr.mli bond_id_repr.ml bond_id_repr.mli vote_repr.ml vote_repr.mli liquidity_baking_repr.ml liquidity_baking_repr.mli diff --git a/src/proto_alpha/lib_protocol/operation_repr.ml b/src/proto_alpha/lib_protocol/operation_repr.ml index 598aa67883570ce44188069e52651880e4ebd945..645760819cd09c98aa1a2738c3e6a9bac9eca073 100644 --- a/src/proto_alpha/lib_protocol/operation_repr.ml +++ b/src/proto_alpha/lib_protocol/operation_repr.ml @@ -125,6 +125,8 @@ module Kind = struct type zk_rollup_publish = Zk_rollup_publish_kind + type zk_rollup_update = Zk_rollup_update_kind + type 'a manager = | Reveal_manager_kind : reveal manager | Transaction_manager_kind : transaction manager @@ -159,6 +161,7 @@ module Kind = struct | Sc_rollup_recover_bond_manager_kind : sc_rollup_recover_bond manager | Zk_rollup_origination_manager_kind : zk_rollup_origination manager | Zk_rollup_publish_manager_kind : zk_rollup_publish manager + | Zk_rollup_update_manager_kind : zk_rollup_update manager end type 'a consensus_operation_type = @@ -469,7 +472,7 @@ and _ manager_operation = -> Kind.sc_rollup_recover_bond manager_operation | Zk_rollup_origination : { public_parameters : Plonk.public_parameters; - circuits_info : bool Zk_rollup_account_repr.SMap.t; + circuits_info : [`Public | `Private | `Fee] Zk_rollup_account_repr.SMap.t; init_state : Zk_rollup_state_repr.t; nb_ops : int; } @@ -479,6 +482,11 @@ and _ manager_operation = ops : (Zk_rollup_operation_repr.t * Zk_rollup_ticket_repr.t option) list; } -> Kind.zk_rollup_publish manager_operation + | Zk_rollup_update : { + zk_rollup : Zk_rollup_repr.t; + update : Zk_rollup_update_repr.t; + } + -> Kind.zk_rollup_update manager_operation let manager_kind : type kind. kind manager_operation -> kind Kind.manager = function @@ -513,6 +521,7 @@ let manager_kind : type kind. kind manager_operation -> kind Kind.manager = | Sc_rollup_recover_bond _ -> Kind.Sc_rollup_recover_bond_manager_kind | Zk_rollup_origination _ -> Kind.Zk_rollup_origination_manager_kind | Zk_rollup_publish _ -> Kind.Zk_rollup_publish_manager_kind + | Zk_rollup_update _ -> Kind.Zk_rollup_update_manager_kind type packed_manager_operation = | Manager : 'kind manager_operation -> packed_manager_operation @@ -614,6 +623,8 @@ let zk_rollup_operation_create_tag = zk_rollup_operation_tag_offset + 0 let zk_rollup_operation_publish_tag = zk_rollup_operation_tag_offset + 1 +let zk_rollup_operation_update_tag = zk_rollup_operation_tag_offset + 2 + module Encoding = struct open Data_encoding @@ -1096,6 +1107,25 @@ module Encoding = struct inj = (fun (zk_rollup, ops) -> Zk_rollup_publish {zk_rollup; ops}); } + let zk_rollup_update_case = + MCase + { + tag = zk_rollup_operation_update_tag; + name = "zk_rollup_update"; + encoding = + obj2 + (req "zk_rollup" Zk_rollup_repr.Address.encoding) + (req "update" Zk_rollup_update_repr.encoding); + select = + (function + | Manager (Zk_rollup_update _ as op) -> Some op | _ -> None); + proj = + (function + | Zk_rollup_update {zk_rollup; update} -> (zk_rollup, update)); + inj = + (fun (zk_rollup, update) -> Zk_rollup_update {zk_rollup; update}); + } + let string_to_bytes_encoding = Data_encoding.conv Bytes.of_string Bytes.to_string Data_encoding.bytes @@ -1728,6 +1758,11 @@ module Encoding = struct zk_rollup_operation_publish_tag Manager_operations.zk_rollup_publish_case + let zk_rollup_update_case = + make_manager_case + zk_rollup_operation_update_tag + Manager_operations.zk_rollup_update_case + let contents_encoding = let make (Case {tag; name; encoding; select; proj; inj}) = case @@ -1781,6 +1816,7 @@ module Encoding = struct make sc_rollup_recover_bond_case; make zk_rollup_origination_case; make zk_rollup_publish_case; + make zk_rollup_update_case; ] let contents_list_encoding = @@ -2042,6 +2078,8 @@ let equal_manager_operation_kind : | Zk_rollup_origination _, _ -> None | Zk_rollup_publish _, Zk_rollup_publish _ -> Some Eq | Zk_rollup_publish _, _ -> None + | Zk_rollup_update _, Zk_rollup_update _ -> Some Eq + | Zk_rollup_update _, _ -> None let equal_contents_kind : type a b. a contents -> b contents -> (a, b) eq option = diff --git a/src/proto_alpha/lib_protocol/operation_repr.mli b/src/proto_alpha/lib_protocol/operation_repr.mli index a52ff693eb4fc248c887ad5d7db41412e71ae849..c5f904322ec97908667e45a91c36d02baf158b45 100644 --- a/src/proto_alpha/lib_protocol/operation_repr.mli +++ b/src/proto_alpha/lib_protocol/operation_repr.mli @@ -51,6 +51,7 @@ - smart contract rollup origination - zk rollup origination - zk rollup publish + - zk rollup update Each of them can be encoded as raw bytes. Operations are distinguished at type level using phantom type parameters. [packed_operation] type allows @@ -156,6 +157,8 @@ module Kind : sig type zk_rollup_publish = Zk_rollup_publish_kind + type zk_rollup_update = Zk_rollup_update_kind + type 'a manager = | Reveal_manager_kind : reveal manager | Transaction_manager_kind : transaction manager @@ -190,6 +193,7 @@ module Kind : sig | Sc_rollup_recover_bond_manager_kind : sc_rollup_recover_bond manager | Zk_rollup_origination_manager_kind : zk_rollup_origination manager | Zk_rollup_publish_manager_kind : zk_rollup_publish manager + | Zk_rollup_update_manager_kind : zk_rollup_update manager end type 'a consensus_operation_type = @@ -558,9 +562,8 @@ and _ manager_operation = -> Kind.sc_rollup_recover_bond manager_operation | Zk_rollup_origination : { public_parameters : Plonk.public_parameters; - circuits_info : bool Zk_rollup_account_repr.SMap.t; - (** Circuit names, alongside a boolean flag indicating - if they can be used for private ops. *) + circuits_info : [`Public | `Private | `Fee] Zk_rollup_account_repr.SMap.t; + (** Circuit names, alongside a tag indicating its kind. *) init_state : Zk_rollup_state_repr.t; nb_ops : int; } @@ -571,6 +574,11 @@ and _ manager_operation = (* See {!Zk_rollup_apply} *) } -> Kind.zk_rollup_publish manager_operation + | Zk_rollup_update : { + zk_rollup : Zk_rollup_repr.t; + update : Zk_rollup_update_repr.t; + } + -> Kind.zk_rollup_update manager_operation type packed_manager_operation = | Manager : 'kind manager_operation -> packed_manager_operation @@ -818,6 +826,8 @@ module Encoding : sig val zk_rollup_publish_case : Kind.zk_rollup_publish Kind.manager case + val zk_rollup_update_case : Kind.zk_rollup_update Kind.manager case + module Manager_operations : sig type 'b case = | MCase : { @@ -889,5 +899,7 @@ module Encoding : sig val zk_rollup_origination_case : Kind.zk_rollup_origination case val zk_rollup_publish_case : Kind.zk_rollup_publish case + + val zk_rollup_update_case : Kind.zk_rollup_update case end end diff --git a/src/proto_alpha/lib_protocol/test/helpers/block.ml b/src/proto_alpha/lib_protocol/test/helpers/block.ml index bd1fb14b4416be26c5b9d776927d432dda5890f0..d5df501c2a0f67f7025b27752122814398e9d616 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/block.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/block.ml @@ -906,7 +906,7 @@ let bake_n_with_all_balance_updates ?(baking_mode = Application) ?policy | Sc_rollup_timeout_result _ | Sc_rollup_execute_outbox_message_result _ | Sc_rollup_recover_bond_result _ | Zk_rollup_origination_result _ - | Zk_rollup_publish_result _ -> + | Zk_rollup_publish_result _ | Zk_rollup_update_result _ -> balance_updates_rev | Transaction_result ( Transaction_to_contract_result {balance_updates; _} @@ -960,7 +960,8 @@ let bake_n_with_origination_results ?(baking_mode = Application) ?policy n b = (Sc_rollup_execute_outbox_message_result _) | Successful_manager_result (Sc_rollup_recover_bond_result _) | Successful_manager_result (Zk_rollup_origination_result _) - | Successful_manager_result (Zk_rollup_publish_result _) -> + | Successful_manager_result (Zk_rollup_publish_result _) + | Successful_manager_result (Zk_rollup_update_result _) -> origination_results_rev | Successful_manager_result (Origination_result x) -> Origination_result x :: origination_results_rev) diff --git a/src/proto_alpha/lib_protocol/test/helpers/dummy_zk_rollup.ml b/src/proto_alpha/lib_protocol/test/helpers/dummy_zk_rollup.ml index 9dbb1ba55ba8e8cd49ba094e15a89039d5ba94b8..3cfbe83f84f9fbedd01df2b92ba94534b0dacb36 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/dummy_zk_rollup.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/dummy_zk_rollup.ml @@ -330,15 +330,26 @@ end) : sig (** Initial state of the rollup *) val init_state : Zk_rollup.State.t - (** Map associating every circuit identifier to a boolean representing - whether the circuit can be part of a private batch *) - val circuits : bool Plonk.Main_protocol.SMap.t + (** Map associating every circuit identifier to its kind *) + val circuits : [`Public | `Private | `Fee] Plonk.Main_protocol.SMap.t (** Commitment to the circuits *) val public_parameters : Plonk.Main_protocol.verifier_public_parameters * Plonk.Main_protocol.transcript + (** [craft_update state ~zk_rollup ?private_ops public_ops] will apply + first the [public_ops], then the [private_ops]. While doing so, + the public inputs for every circuit will be collected. A Plonk proof + of correctness of the application these operations is created. *) + val craft_update : + Zk_rollup.State.t -> + zk_rollup:Zk_rollup.t -> + ?private_ops:Zk_rollup.Operation.t list list -> + ?exit_validities:bool list -> + Zk_rollup.Operation.t list -> + Zk_rollup.State.t * Zk_rollup.Update.t + module Internal_for_tests : sig val true_op : Zk_rollup.Operation.t @@ -347,6 +358,8 @@ end) : sig val pending : Zk_rollup.Operation.t list val private_ops : Zk_rollup.Operation.t list list + + val update_data : Zk_rollup.Update.t end end = struct open Protocol.Alpha_context @@ -368,7 +381,7 @@ end = struct let dummy_ticket_hash = Bytes.make 32 '0' - let _of_proto_state : Zk_rollup.State.t -> Types.P.state = + let of_proto_state : Zk_rollup.State.t -> Types.P.state = fun s -> Bls12_381.Fr.is_one s.(0) let to_proto_state : Types.P.state -> Zk_rollup.State.t = @@ -415,9 +428,9 @@ end = struct ] let circuits = - SMap.(add "op" false @@ add batch_name true @@ add "fee" false empty) + SMap.(add "op" `Public @@ add batch_name `Private @@ add "fee" `Fee empty) - let public_parameters, _prover_pp = + let public_parameters, prover_pp = let (ppp, vpp), t = Plonk.Main_protocol.setup_multi_circuits ~zero_knowledge:false @@ -426,11 +439,147 @@ end = struct in ((vpp, t), ppp) - let _insert s x m = + let insert s x m = match SMap.find_opt s m with | None -> SMap.add s [x] m | Some l -> SMap.add s (x :: l) m + let craft_update : + Zk_rollup.State.t -> + zk_rollup:Zk_rollup.t -> + ?private_ops:Zk_rollup.Operation.t list list -> + ?exit_validities:bool list -> + Zk_rollup.Operation.t list -> + Zk_rollup.State.t * Zk_rollup.Update.t = + fun s ~zk_rollup ?(private_ops = []) ?exit_validities pending -> + let s = of_proto_state s in + let rev_inputs = SMap.empty in + let exit_validities = + match exit_validities with + | None -> List.map (Fun.const true) pending + | Some l -> + assert (List.length l = List.length pending) ; + l + in + let _circ, _pi_size, op_solver = SMap.find "op" circuit_map in + (* Process the public operations *) + let s, rev_inputs, rev_pending_pis = + Stdlib.List.fold_left2 + (fun (s, rev_inputs, rev_pending_pis) op exit_validity -> + let new_state = + if s = of_proto_state Zk_rollup.Operation.(op.payload) then not s + else s + in + let fee = Bls12_381.Fr.zero in + let pi_to_send = + Zk_rollup.Update. + {new_state = to_proto_state new_state; fee; exit_validity} + in + let exit_validity_s = + if exit_validity then Bls12_381.Fr.one else Bls12_381.Fr.zero + in + let public_inputs = + Array.concat + [ + to_proto_state s; + to_proto_state new_state; + [|fee; exit_validity_s; Zk_rollup.to_scalar zk_rollup|]; + Zk_rollup.Operation.to_scalar_array op; + ] + in + let private_inputs = Solver.solve op_solver public_inputs in + ( new_state, + insert + "op" + Plonk.Main_protocol. + {public = public_inputs; witness = private_inputs} + rev_inputs, + ("op", pi_to_send) :: rev_pending_pis )) + (s, rev_inputs, []) + pending + exit_validities + in + let pending_pis = List.rev rev_pending_pis in + + let _circ, _pi_size, batch_solver = SMap.find batch_name circuit_map in + (* Process the private operation batches *) + let s, rev_inputs, rev_private_pis = + if private_ops = [] then (s, rev_inputs, []) + else + List.fold_left + (fun (s, rev_inputs, rev_private_pis) batch -> + let new_state = + List.fold_left + (fun s op -> + if s = of_proto_state Zk_rollup.Operation.(op.payload) then + not s + else s) + s + batch + in + let fees = Bls12_381.Fr.zero in + let pi_to_send : Zk_rollup.Update.private_inner_pi = + Zk_rollup.Update.{new_state = to_proto_state new_state; fees} + in + let public_inputs = + Array.concat + [ + to_proto_state s; + to_proto_state new_state; + [|fees; Zk_rollup.to_scalar zk_rollup|]; + ] + in + let initial = + Array.concat + ([public_inputs] + @ List.map Zk_rollup.Operation.to_scalar_array batch) + in + let private_inputs = Solver.solve batch_solver initial in + ( new_state, + insert + batch_name + Plonk.Main_protocol. + {public = public_inputs; witness = private_inputs} + rev_inputs, + (batch_name, pi_to_send) :: rev_private_pis )) + (s, rev_inputs, []) + private_ops + in + let private_pis = List.rev rev_private_pis in + (* Dummy fee circuit *) + let _circ, _pi_size, fee_solver = SMap.find "fee" circuit_map in + let rev_inputs, fee_pi = + let fee_pi = Zk_rollup.Update.{new_state = to_proto_state s} in + let fees = Bls12_381.Fr.zero in + + let public_inputs = + Array.concat [to_proto_state s; to_proto_state s; [|fees|]] + in + let private_inputs = Solver.solve fee_solver public_inputs in + ( insert + "fee" + Plonk.Main_protocol.{public = public_inputs; witness = private_inputs} + rev_inputs, + fee_pi ) + in + let (_, t), pp = (public_parameters, prover_pp) in + let inputs = SMap.map List.rev rev_inputs in + + let proof, _ = Plonk.Main_protocol.prove_multi_circuits (pp, t) ~inputs in + let public_inputs = + Plonk.Main_protocol.SMap.map + (List.map (fun Plonk.Main_protocol.{public; witness = _} -> public)) + inputs + in + assert ( + fst + @@ Plonk.Main_protocol.verify_multi_circuits + public_parameters + ~public_inputs + proof) ; + ( to_proto_state s, + Zk_rollup.Update.{pending_pis; private_pis; fee_pi; proof} ) + let init_state = to_proto_state false module Internal_for_tests = struct @@ -466,5 +615,16 @@ end = struct Stdlib.List.init n_batches @@ Fun.const @@ Stdlib.List.init Params.batch_size (fun i -> if i mod 2 = 0 then false_op else true_op) + + let update_data = + snd + @@ craft_update + init_state + ~zk_rollup: + (Data_encoding.Binary.of_bytes_exn + Zk_rollup.Address.encoding + dummy_rollup_id) + ~private_ops + pending end end diff --git a/src/proto_alpha/lib_protocol/test/helpers/op.ml b/src/proto_alpha/lib_protocol/test/helpers/op.ml index 77943cf8968029ce63926da8545841cd31367f59..474269a4058e94afdae0c7ab9df885641df0cfd7 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/op.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/op.ml @@ -1044,3 +1044,18 @@ let zk_rollup_publish ?force_reveal ?counter ?fee ?gas_limit ?storage_limit ctxt >>=? fun to_sign_op -> Context.Contract.manager ctxt src >|=? fun account -> sign account.sk ctxt to_sign_op + +let zk_rollup_update ?force_reveal ?counter ?fee ?gas_limit ?storage_limit ctxt + (src : Contract.t) ~zk_rollup ~update = + manager_operation + ?force_reveal + ?counter + ?fee + ?gas_limit + ?storage_limit + ~source:src + ctxt + (Zk_rollup_update {zk_rollup; update}) + >>=? fun to_sign_op -> + Context.Contract.manager ctxt src >|=? fun account -> + sign account.sk ctxt to_sign_op diff --git a/src/proto_alpha/lib_protocol/test/helpers/op.mli b/src/proto_alpha/lib_protocol/test/helpers/op.mli index 4b9df5708427d82fd52cced1afa55e7477d5042e..58806cb4fe9abeeae2e268f21574c289b6d708fc 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/op.mli +++ b/src/proto_alpha/lib_protocol/test/helpers/op.mli @@ -743,7 +743,7 @@ val zk_rollup_origination : public_parameters: Plonk.Main_protocol.verifier_public_parameters * Plonk.Main_protocol.transcript -> - circuits_info:bool Zk_rollup.Account.SMap.t -> + circuits_info:[`Public | `Private | `Fee] Zk_rollup.Account.SMap.t -> init_state:Zk_rollup.State.t -> nb_ops:int -> (Operation.packed * Zk_rollup.t) tzresult Lwt.t @@ -779,3 +779,17 @@ val zk_rollup_publish : zk_rollup:Zk_rollup.t -> ops:(Zk_rollup.Operation.t * Zk_rollup.Ticket.t option) list -> Operation.packed tzresult Lwt.t + +(** [zk_rollup_update ctxt source ~zk_rollup ~update] tries to apply an update + to a ZK Rollup. *) +val zk_rollup_update : + ?force_reveal:bool -> + ?counter:Manager_counter.t -> + ?fee:Tez.t -> + ?gas_limit:gas_limit -> + ?storage_limit:Z.t -> + Context.t -> + Contract.t -> + zk_rollup:Zk_rollup.t -> + update:Zk_rollup.Update.t -> + Operation.packed tzresult Lwt.t diff --git a/src/proto_alpha/lib_protocol/test/integration/operations/test_zk_rollup.ml b/src/proto_alpha/lib_protocol/test/integration/operations/test_zk_rollup.ml index ce91e84403782b72d017300cc1e625f333e6bf3d..abc0f9bba3a17003293cdfede98304a1f2ef8c14 100644 --- a/src/proto_alpha/lib_protocol/test/integration/operations/test_zk_rollup.ml +++ b/src/proto_alpha/lib_protocol/test/integration/operations/test_zk_rollup.ml @@ -740,6 +740,474 @@ let test_invalid_deposit () = in return_unit +(* Test for a valid update: + - 1 private batch of 10 "true" operations + - 1 public "false" operation + On a ZKRU with the initial state and a pending list with + 1 operation ("false"). +*) +let test_update () = + let* b, contracts, zk_rollup, pkh = init_with_pending 1 in + let contract = Stdlib.List.hd contracts in + let* i = Incremental.begin_construction b in + let n_batches = 2 in + let _, update = + Operator.( + craft_update + init_state + ~zk_rollup + ~private_ops: + (Stdlib.List.init n_batches (fun batch -> + Stdlib.List.init batch_size + @@ Fun.const + @@ (if batch mod 2 = 0 then true_op else false_op) pkh zk_rollup)) + [false_op pkh zk_rollup]) + in + let* operation = Op.zk_rollup_update (I i) contract ~zk_rollup ~update in + let* _i = Incremental.add_operation i operation in + return_unit + +(* Test for an invalid update: + - 1 public "true" operation + On a ZKRU with the initial state and a pending list with + 1 operation ("false"). + The public operation proved is different from the one in the pending list. +*) +let test_update_false_proof () = + let* b, contracts, zk_rollup, pkh = init_with_pending 1 in + let contract = Stdlib.List.hd contracts in + let* i = Incremental.begin_construction b in + (* Testing with proof on incorrect statement *) + let _, update = + Operator.(craft_update init_state ~zk_rollup [true_op pkh zk_rollup]) + in + let* operation = Op.zk_rollup_update (I i) contract ~zk_rollup ~update in + let* _i = + Incremental.add_operation + i + ~expect_apply_failure: + (check_proto_error Zk_rollup.Errors.Invalid_verification) + operation + in + (* Testing with proof with incorrect private inputs *) + let update = + let _, Zk_rollup.Update.{pending_pis; private_pis; fee_pi; proof} = + Operator.(craft_update init_state ~zk_rollup [true_op pkh zk_rollup]) + in + let private_pis = List.rev private_pis in + Zk_rollup.Update.{pending_pis; private_pis; fee_pi; proof} + in + let* operation = Op.zk_rollup_update (I i) contract ~zk_rollup ~update in + let* _i = + Incremental.add_operation + i + ~expect_apply_failure: + (check_proto_error Zk_rollup.Errors.Invalid_verification) + operation + in + return_unit + +(* Test for an invalid update: + A set of inputs for a public circuit is included in the list of + inputs for private batches. +*) +let test_update_public_in_private () = + let* b, contracts, zk_rollup, pkh = init_with_pending 1 in + let contract = Stdlib.List.hd contracts in + let* i = Incremental.begin_construction b in + let _, update = + Operator.(craft_update init_state ~zk_rollup [true_op pkh zk_rollup]) + in + let update = + (* Circuit ID and inputs for a public circuit, which will be added to the + [private_pis] list *) + let name, op_pi = Stdlib.List.hd update.pending_pis in + { + update with + private_pis = + (name, {new_state = op_pi.new_state; fees = op_pi.fee}) + :: update.private_pis; + } + in + let* operation = Op.zk_rollup_update (I i) contract ~zk_rollup ~update in + let* _i = + Incremental.add_operation + i + ~expect_apply_failure:(check_proto_error Zk_rollup.Errors.Invalid_circuit) + operation + in + return_unit + +(* Test for an invalid update: + Two ZKRUs are originated: [zk_rollup1] and [zk_rollup2]. + An L2 [op] for [zk_rollup2] is appended to [zk_rollup1]'s pending list. + This operation must be discarded, but a malicious validator tries to process + it by making a proof for an update in which [op]'s [rollup_id] is changed + from [zk_rollup2] to [zk_rollup1]. The verification must fail, because + the Protocol uses the actual [op] from the pending list as input. +*) +let test_update_for_another_rollup () = + let* b, contracts, zk_rollup1, pkh = init_with_pending 3 in + let contract0 = Stdlib.List.hd contracts in + let contract1 = Stdlib.List.nth contracts 1 in + let contract2 = Stdlib.List.nth contracts 2 in + let* i = Incremental.begin_construction b in + (* Originate [zk_rollup2] *) + let* operation, zk_rollup2 = + Op.zk_rollup_origination + (I i) + contract0 + ~public_parameters:Operator.public_parameters + ~circuits_info:(of_plonk_smap Operator.circuits) + ~init_state:Operator.init_state + ~nb_ops:1 + in + let* i = Incremental.add_operation i operation in + (* Append to [zk_rollup1] an op for [zk_rollup2] *) + let* operation = + Op.zk_rollup_publish + (I i) + contract1 + ~zk_rollup:zk_rollup1 + ~ops:[no_ticket @@ true_op pkh zk_rollup2] + in + let* i = Incremental.add_operation i operation in + (* Craft the update, changing the "true" op to have zk_rollup1 as + [rollup_id] *) + let _, update = + Operator.( + craft_update + init_state + ~zk_rollup:zk_rollup1 + [false_op pkh zk_rollup1; true_op pkh zk_rollup1]) + in + let* operation = + Op.zk_rollup_update (I i) contract2 ~zk_rollup:zk_rollup1 ~update + in + let* _i = + Incremental.add_operation + ~expect_apply_failure: + (check_proto_error Zk_rollup.Errors.Invalid_verification) + i + operation + in + return_unit + +(* Test for an invalid update: + The update sent by the prover processes more public operations than + those in the pending list. +*) +let test_update_more_public_than_pending () = + (* test with number of pending operations < min_pending_to_process. *) + let* b, contracts, zk_rollup, pkh = init_with_pending 1 in + let contract = Stdlib.List.hd contracts in + let* i = Incremental.begin_construction b in + let _, update = + Operator.( + craft_update + init_state + ~zk_rollup + [false_op pkh zk_rollup; true_op pkh zk_rollup]) + in + let* operation = Op.zk_rollup_update (I i) contract ~zk_rollup ~update in + let* _i = + Incremental.add_operation + ~expect_apply_failure: + (check_proto_error Zk_rollup_storage.Zk_rollup_pending_list_too_short) + i + operation + in + (* test with number of pending operations >= min_pending_to_process. *) + let* constants = Context.get_constants (I i) in + let min_pending_to_process = + constants.parametric.zk_rollup.min_pending_to_process + in + let* b, contracts, zk_rollup, pkh = + init_with_pending ~n_pending:min_pending_to_process 1 + in + let contract = Stdlib.List.hd contracts in + let* i = Incremental.begin_construction b in + let _, update = + Operator.( + craft_update + init_state + ~zk_rollup + (Stdlib.List.init (min_pending_to_process + 1) (fun i -> + if i mod 2 = 0 then false_op pkh zk_rollup + else true_op pkh zk_rollup))) + in + let* operation = Op.zk_rollup_update (I i) contract ~zk_rollup ~update in + let* _i = + Incremental.add_operation + ~expect_apply_failure: + (check_proto_error Zk_rollup_storage.Zk_rollup_pending_list_too_short) + i + operation + in + return_unit + +(* Test for an invalid update: + The update sent by the prover contains a set of circuit inputs in which + the [new_state] is larger than the ZKRU's [state_length]. +*) +let test_update_inconsistent_state () = + let* b, contracts, zk_rollup, pkh = init_with_pending 1 in + let contract = Stdlib.List.hd contracts in + let* i = Incremental.begin_construction b in + let _, update = + Operator.(craft_update init_state ~zk_rollup [false_op pkh zk_rollup]) + in + let open Zk_rollup.Update in + let update = + { + update with + pending_pis = + List.map + (fun (s, (op_pi : op_pi)) -> + ( s, + { + op_pi with + new_state = Array.append op_pi.new_state op_pi.new_state; + } )) + update.pending_pis; + } + in + let* operation = Op.zk_rollup_update (I i) contract ~zk_rollup ~update in + let* _i = + Incremental.add_operation + ~expect_apply_failure: + (check_proto_error Zk_rollup.Errors.Inconsistent_state_update) + i + operation + in + return_unit + +(* Test for an invalid update: + The update sent by the prover processes fewer pending operations (p.o.) than + allowed (the exact number of p.o. or at least min_pending_to_process). + The pending list has a length of 2, while only 1 is processed. +*) +let test_update_not_enough_pending () = + let* b, contracts, zk_rollup, pkh = init_with_pending ~n_pending:2 1 in + let contract = Stdlib.List.hd contracts in + let* i = Incremental.begin_construction b in + let _, update = + Operator.(craft_update init_state ~zk_rollup [false_op pkh zk_rollup]) + in + let* operation = Op.zk_rollup_update (I i) contract ~zk_rollup ~update in + let* _i = + Incremental.add_operation + ~expect_apply_failure:(check_proto_error Zk_rollup.Errors.Pending_bound) + i + operation + in + return_unit + +(* Test for a valid update: + The update sent by the prover processes a prefix of the pending list, + of the minimum length allowed. +*) +let test_update_valid_prefix () = + (* Checking when pending list has more than min_pending_to_process *) + let min_pending_to_process = + Context.default_test_constants.zk_rollup.min_pending_to_process + in + let* b, contracts, zk_rollup, pkh = + init_with_pending ~n_pending:(min_pending_to_process + 1) 1 + in + let contract = Stdlib.List.hd contracts in + let* i = Incremental.begin_construction b in + let _, update = + Operator.( + craft_update + init_state + ~zk_rollup + (Stdlib.List.init min_pending_to_process (fun i -> + if i mod 2 = 0 then false_op pkh zk_rollup + else true_op pkh zk_rollup))) + in + (* Checking when pending list has less than min_pending_to_process *) + let* operation = Op.zk_rollup_update (I i) contract ~zk_rollup ~update in + let* _i = Incremental.add_operation i operation in + let* b, contracts, zk_rollup, pkh = init_with_pending ~n_pending:2 1 in + let contract = Stdlib.List.hd contracts in + let* i = Incremental.begin_construction b in + let _, update = + Operator.( + craft_update + init_state + ~zk_rollup + [false_op pkh zk_rollup; true_op pkh zk_rollup]) + in + let* operation = Op.zk_rollup_update (I i) contract ~zk_rollup ~update in + let* _i = Incremental.add_operation i operation in + return_unit + +let test_valid_deposit_and_withdrawal () = + (* Create 2 accounts and one zk rollups *) + let* block, contracts, zk_rollup = init_and_originate 2 in + let contract0 = Stdlib.List.nth contracts 0 in + let contract1 = Stdlib.List.nth contracts 1 in + (* Create and originate the deposit contract *) + let module Nat_ticket = Nat_ticket (struct + let contents = 1 + end) in + let* deposit_contract, _script, block = + Nat_ticket.init_deposit_contract (Z.of_int 10) block contract0 + in + let token = Nat_ticket.ex_token ~ticketer:deposit_contract in + (* Generate ticket created by deposit contract and owned by rollup *) + let* ticket_hash = + Nat_ticket.ticket_hash (B block) ~ticketer:deposit_contract ~zk_rollup + in + let pkh = match contract0 with Implicit pkh -> pkh | _ -> assert false in + (* Create append/deposit operation with ticket *) + let zk_op = + { + (false_op pkh zk_rollup) with + price = {id = ticket_hash; amount = Z.of_int 10}; + } + in + let* operation = + Nat_ticket.deposit_op + ~block + ~zk_rollup + ~zk_op + ~account:contract0 + ~deposit_contract + in + (* ----- Start generating block *) + let* i = Incremental.begin_construction block in + (* check rollup exists with none of these particular tokens *) + let* () = + assert_ticket_balance ~loc:__LOC__ i token (Zk_rollup zk_rollup) None + in + (* ----- Add deposit operation to block*) + let* i = Incremental.add_operation i operation in + (* check *rollup* has 10 of these particular tokens (deposit has been processed) *) + let* () = + assert_ticket_balance ~loc:__LOC__ i token (Zk_rollup zk_rollup) (Some 10) + in + (* check *contract* has no tokens (deposit has been processed) *) + let* () = + assert_ticket_balance ~loc:__LOC__ i token (Contract contract0) None + in + (* Create update operation to process the zk operation (which is a + "deposit-withdrawal" for dummy rollup) *) + let _, update = + Operator.(craft_update init_state ~zk_rollup ~private_ops:[] [zk_op]) + in + let* operation = Op.zk_rollup_update (I i) contract1 ~zk_rollup ~update in + (* ----- Add update operation to block) *) + let* i = Incremental.add_operation i operation in + (* check *rollup* has no tokens (deposit was withdrawn) *) + let* () = + assert_ticket_balance ~loc:__LOC__ i token (Zk_rollup zk_rollup) None + in + (* check *contract* has 10 of these particular tokens (deposit was withdrawn) *) + let* () = + assert_ticket_balance ~loc:__LOC__ i token (Contract contract0) (Some 10) + in + return_unit + +let test_valid_deposit_and_external_withdrawal () = + (* Create 2 accounts and one zk rollups *) + let* block, contracts, zk_rollup = init_and_originate 4 in + let contract0 = Stdlib.List.nth contracts 0 in + let contract1 = Stdlib.List.nth contracts 1 in + let contract2 = Stdlib.List.nth contracts 2 in + let contract3 = Stdlib.List.nth contracts 3 in + (* Create and originate the deposit contract *) + let module Nat_ticket = Nat_ticket (struct + let contents = 1 + end) in + let* deposit_contract, _script, block = + Nat_ticket.init_deposit_contract (Z.of_int 10) block contract0 + in + let token = Nat_ticket.ex_token ~ticketer:deposit_contract in + (* Generate ticket created by deposit contract and owned by rollup *) + let* ticket_hash = + Nat_ticket.ticket_hash (B block) ~ticketer:deposit_contract ~zk_rollup + in + let pkh = match contract0 with Implicit pkh -> pkh | _ -> assert false in + (* Create append/deposit operation with ticket *) + let zk_op = + { + (false_op pkh zk_rollup) with + price = {id = ticket_hash; amount = Z.of_int 10}; + } + in + let* operation = + Nat_ticket.deposit_op + ~block + ~zk_rollup + ~zk_op + ~account:contract0 + ~deposit_contract + in + (* ----- Start generating block *) + let* i = Incremental.begin_construction block in + (* check rollup exists with none of these particular tokens *) + let* () = + assert_ticket_balance ~loc:__LOC__ i token (Zk_rollup zk_rollup) None + in + (* ----- Add deposit operation to block*) + let* i = Incremental.add_operation i operation in + (* check *rollup* has 10 of these particular tokens (deposit has been processed) *) + let* () = + assert_ticket_balance ~loc:__LOC__ i token (Zk_rollup zk_rollup) (Some 10) + in + (* check *contract* has no tokens (deposit has been processed) *) + let* () = + assert_ticket_balance ~loc:__LOC__ i token (Contract contract0) None + in + (* Create update operation to process the zk operation (which is a + "deposit" for dummy rollup) *) + let s, update = + Operator.( + craft_update + init_state + ~zk_rollup + ~private_ops:[] + ~exit_validities:[false] + [zk_op]) + in + let* operation = Op.zk_rollup_update (I i) contract1 ~zk_rollup ~update in + (* ----- Add update operation to block) *) + let* i = Incremental.add_operation i operation in + (* check *rollup* has 10 of these particular tokens (deposit has been processed) *) + let* () = + assert_ticket_balance ~loc:__LOC__ i token (Zk_rollup zk_rollup) (Some 10) + in + (* Create withdrawal operation with ticket *) + let zk_op = + { + (false_op pkh zk_rollup) with + price = {id = ticket_hash; amount = Z.of_int (-10)}; + } + in + let ticket = Nat_ticket.zkru_ticket ~ticketer:deposit_contract in + let* operation = + Op.zk_rollup_publish (I i) contract2 ~zk_rollup ~ops:[(zk_op, Some ticket)] + in + let* i = Incremental.add_operation i operation in + (* Create update to process the withdrawal *) + let _, update = + Operator.( + craft_update s ~zk_rollup ~private_ops:[] ~exit_validities:[true] [zk_op]) + in + let* operation = Op.zk_rollup_update (I i) contract3 ~zk_rollup ~update in + let* i = Incremental.add_operation i operation in + (* check *rollup* has no tokens *) + let* () = + assert_ticket_balance ~loc:__LOC__ i token (Zk_rollup zk_rollup) None + in + (* check *contract* has 10 of these particular tokens *) + let* () = + assert_ticket_balance ~loc:__LOC__ i token (Contract contract0) (Some 10) + in + return_unit + let tests = [ Tztest.tztest @@ -759,4 +1227,32 @@ let tests = Tztest.tztest "append external deposit" `Quick test_append_external_deposit; Tztest.tztest "append check errors" `Quick test_append_errors; Tztest.tztest "invalid deposit" `Quick test_invalid_deposit; + Tztest.tztest "update" `Quick test_update; + Tztest.tztest "update with false proof" `Quick test_update_false_proof; + Tztest.tztest + "update with invalid circuit" + `Quick + test_update_public_in_private; + Tztest.tztest + "update with op for another rollup" + `Quick + test_update_for_another_rollup; + Tztest.tztest + "update with more public operations than pending" + `Quick + test_update_more_public_than_pending; + Tztest.tztest + "update with inconsistent state" + `Quick + test_update_inconsistent_state; + Tztest.tztest + "update with not enough pending" + `Quick + test_update_not_enough_pending; + Tztest.tztest "update with valid prefix" `Quick test_update_valid_prefix; + Tztest.tztest "valid deposit" `Quick test_valid_deposit_and_withdrawal; + Tztest.tztest + "valid deposit and external withdrawal" + `Quick + test_valid_deposit_and_external_withdrawal; ] diff --git a/src/proto_alpha/lib_protocol/test/integration/validate/manager_operation_helpers.ml b/src/proto_alpha/lib_protocol/test/integration/validate/manager_operation_helpers.ml index 12e32d4e7cddeb736c06dc63835d0cc4371882bf..9a57b3e7676d3a5df81f277977d1d6a314fcef28 100644 --- a/src/proto_alpha/lib_protocol/test/integration/validate/manager_operation_helpers.ml +++ b/src/proto_alpha/lib_protocol/test/integration/validate/manager_operation_helpers.ml @@ -104,6 +104,7 @@ type manager_operation_kind = | K_Dal_publish_slot_header | K_Zk_rollup_origination | K_Zk_rollup_publish + | K_Zk_rollup_update (** The requirements for a tested manager operation. *) type operation_req = { @@ -206,6 +207,7 @@ let kind_to_string = function | K_Dal_publish_slot_header -> "Dal_publish_slot_header" | K_Zk_rollup_origination -> "Zk_rollup_origination" | K_Zk_rollup_publish -> "Zk_rollup_publish" + | K_Zk_rollup_update -> "Zk_rollup_update" (** {2 Pretty-printers} *) let pp_opt pp v = @@ -1181,6 +1183,23 @@ let mk_zk_rollup_publish (oinfos : operation_req) (infos : infos) = in return op +let mk_zk_rollup_update (oinfos : operation_req) (infos : infos) = + let open Lwt_result_syntax in + let* zk_rollup = zk_rollup_of infos.ctxt.zk_rollup in + let* op = + Op.zk_rollup_update + ?fee:oinfos.fee + ?gas_limit:oinfos.gas_limit + ?counter:oinfos.counter + ?storage_limit:oinfos.storage_limit + ?force_reveal:oinfos.force_reveal + (B infos.ctxt.block) + (contract_of (get_source infos)) + ~zk_rollup + ~update:ZKOperator.Internal_for_tests.update_data + in + return op + (** {2 Helpers for generation of generic check tests by manager operation} *) (** Generic forge for any kind of manager operation according to @@ -1218,6 +1237,7 @@ let select_op (op_req : operation_req) (infos : infos) = | K_Dal_publish_slot_header -> mk_dal_publish_slot_header | K_Zk_rollup_origination -> mk_zk_rollup_origination | K_Zk_rollup_publish -> mk_zk_rollup_publish + | K_Zk_rollup_update -> mk_zk_rollup_update in mk_op op_req infos @@ -1574,6 +1594,8 @@ let subjects = K_Sc_rollup_recover_bond; K_Dal_publish_slot_header; K_Zk_rollup_origination; + K_Zk_rollup_publish; + K_Zk_rollup_update; ] let is_consumer = function @@ -1585,7 +1607,7 @@ let is_consumer = function | K_Sc_rollup_refute | K_Sc_rollup_timeout | K_Sc_rollup_cement | K_Sc_rollup_publish | K_Sc_rollup_execute_outbox_message | K_Sc_rollup_recover_bond | K_Dal_publish_slot_header - | K_Zk_rollup_origination | K_Zk_rollup_publish -> + | K_Zk_rollup_origination | K_Zk_rollup_publish | K_Zk_rollup_update -> false | K_Transaction | K_Origination | K_Register_global_constant | K_Tx_rollup_dispatch_tickets | K_Transfer_ticket -> @@ -1612,4 +1634,5 @@ let is_disabled flags = function | K_Sc_rollup_execute_outbox_message | K_Sc_rollup_recover_bond -> flags.scoru = false | K_Dal_publish_slot_header -> flags.dal = false - | K_Zk_rollup_origination | K_Zk_rollup_publish -> flags.zkru = false + | K_Zk_rollup_origination | K_Zk_rollup_publish | K_Zk_rollup_update -> + flags.zkru = false diff --git a/src/proto_alpha/lib_protocol/test/integration/validate/test_sanity.ml b/src/proto_alpha/lib_protocol/test/integration/validate/test_sanity.ml index a148921e7aed4673ff73296513f8195e7a7b81c9..b06dfa1f454fab43fffbebf0629053d29c3c645b 100644 --- a/src/proto_alpha/lib_protocol/test/integration/validate/test_sanity.ml +++ b/src/proto_alpha/lib_protocol/test/integration/validate/test_sanity.ml @@ -86,7 +86,8 @@ let ensure_kind infos kind = | Sc_rollup_recover_bond _, K_Sc_rollup_recover_bond | Dal_publish_slot_header _, K_Dal_publish_slot_header | Zk_rollup_origination _, K_Zk_rollup_origination - | Zk_rollup_publish _, K_Zk_rollup_publish -> + | Zk_rollup_publish _, K_Zk_rollup_publish + | Zk_rollup_update _, K_Zk_rollup_update -> return_unit | ( ( Transaction _ | Origination _ | Register_global_constant _ | Delegation _ | Set_deposits_limit _ | Update_consensus_key _ @@ -99,7 +100,7 @@ let ensure_kind infos kind = | Sc_rollup_refute _ | Sc_rollup_timeout _ | Sc_rollup_execute_outbox_message _ | Sc_rollup_recover_bond _ | Dal_publish_slot_header _ | Zk_rollup_origination _ - | Zk_rollup_publish _ ), + | Zk_rollup_publish _ | Zk_rollup_update _ ), _ ) -> assert false) | Single _ -> assert false diff --git a/src/proto_alpha/lib_protocol/test/unit/test_zk_rollup_storage.ml b/src/proto_alpha/lib_protocol/test/unit/test_zk_rollup_storage.ml index eee8701710b1856252e1e73e098953e0226cc779..a6288a63f16fb56b86da75e669de0b2a86f8ad54 100644 --- a/src/proto_alpha/lib_protocol/test/unit/test_zk_rollup_storage.ml +++ b/src/proto_alpha/lib_protocol/test/unit/test_zk_rollup_storage.ml @@ -212,6 +212,164 @@ module Raw_context_tests = struct let*! e = Zk_rollup_storage.add_to_pending ctx address [op] >>= wrap in let expected_message = "Storage error (fatal internal error)" in Assert.proto_error_with_info ~loc:__LOC__ e expected_message + + (* Check that the [get_prefix] helper actually returns a list of the + desired length. *) + let pending_list_get () = + let open Lwt_result_syntax in + let* ctx, rollup, _contract = originate_ctx () in + let pkh, _pk, _sk = Signature.generate_key () in + let op = + no_ticket + Zk_rollup_operation_repr. + { + op_code = 0; + price = {id = Ticket_hash_repr.zero; amount = Z.zero}; + l1_dst = pkh; + rollup_id = rollup; + payload = [|Bls12_381.Fr.one|]; + } + in + let** ctx, _size = Zk_rollup_storage.add_to_pending ctx rollup [op; op] in + let** _ctx, prefix = Zk_rollup_storage.get_prefix ctx rollup 1 in + assert (List.length prefix = 1) ; + return_unit + + (* Check the [get_prefix] errors. *) + let pending_list_errors () = + let open Lwt_result_syntax in + let* ctx, rollup, _contract = originate_ctx () in + let pkh, _pk, _sk = Signature.generate_key () in + let op = + no_ticket + Zk_rollup_operation_repr. + { + op_code = 0; + price = {id = Ticket_hash_repr.zero; amount = Z.zero}; + l1_dst = pkh; + rollup_id = rollup; + payload = [|Bls12_381.Fr.one|]; + } + in + (* Initialise the pending list with 2 operations *) + let** ctx, _size = Zk_rollup_storage.add_to_pending ctx rollup [op; op] in + (* Check that retrieving too many ops returns an error *) + let*! e = Zk_rollup_storage.get_prefix ctx rollup 3 >>= wrap in + let* () = + Assert.proto_error_with_info ~loc:__LOC__ e "Pending list is too short" + in + (* Check that retrieving a negative number of ops returns an error *) + let*! e = Zk_rollup_storage.get_prefix ctx rollup (-1) >>= wrap in + let* () = + Assert.proto_error_with_info + ~loc:__LOC__ + e + "Negative length for pending list prefix" + in + (* Check that get prefix fails with invalid zkru address *) + let* _ctx, nonce = Raw_context.increment_origination_nonce ctx |> wrap in + let* address = + Zk_rollup_repr.Address.from_nonce (Origination_nonce.incr nonce) |> wrap + in + let*! e = Zk_rollup_storage.get_prefix ctx address (-1) >>= wrap in + Assert.proto_error_with_info + ~loc:__LOC__ + e + "Storage error (fatal internal error)" + + (* Check that the [update] helper correctly removes a prefix of the + pending list (both in the descriptor and the actual operations storage). + *) + let test_update () = + let open Lwt_result_syntax in + (* Originate rollup and contract *) + let* ctx, rollup, contract = originate_ctx () in + let pkh = + match contract with Originated _ -> assert false | Implicit pkh -> pkh + in + let op = + no_ticket + Zk_rollup_operation_repr. + { + op_code = 0; + price = {id = Ticket_hash_repr.zero; amount = Z.zero}; + l1_dst = pkh; + rollup_id = rollup; + payload = [|Bls12_381.Fr.one|]; + } + in + (* Populate rollup with 2 ops *) + let** ctx, _size = Zk_rollup_storage.add_to_pending ctx rollup [op; op] in + let** ctx, acc = Storage.Zk_rollup.Account.get ctx rollup in + (* Processing first pending op *) + let** ctx = + Zk_rollup_storage.update ctx rollup ~pending_to_drop:1 ~new_account:acc + in + (* Check that op at index 0 has been removed *) + let** ctx, opt = + Storage.Zk_rollup.Pending_operation.find (ctx, rollup) 0L + in + assert (Option.is_none opt) ; + (* Check that pending list still has one op *) + let** ctx, pending = Storage.Zk_rollup.Pending_list.get ctx rollup in + assert (Helpers.pending_length pending = 1) ; + let* _ctx, ops = Helpers.get_pending_list ctx rollup pending in + assert (List.length ops = 1) ; + return_unit + + let test_update_errors () = + let open Lwt_result_syntax in + (* Originate rollup and contract *) + let* ctx, rollup, contract = originate_ctx () in + let pkh = + match contract with Originated _ -> assert false | Implicit pkh -> pkh + in + let op = + no_ticket + Zk_rollup_operation_repr. + { + op_code = 0; + price = {id = Ticket_hash_repr.zero; amount = Z.zero}; + l1_dst = pkh; + rollup_id = rollup; + payload = [|Bls12_381.Fr.one|]; + } + in + (* Populate rollup with 2 ops *) + let** ctx, _size = Zk_rollup_storage.add_to_pending ctx rollup [op; op] in + let** ctx, acc = Storage.Zk_rollup.Account.get ctx rollup in + (* Processing too many ops *) + let*! e = + Zk_rollup_storage.update ctx rollup ~pending_to_drop:3 ~new_account:acc + >>= wrap + in + let* () = + Assert.proto_error_with_info ~loc:__LOC__ e "Pending list is too short" + in + (* Processing negative number of ops *) + let*! e = + Zk_rollup_storage.update ctx rollup ~pending_to_drop:(-3) ~new_account:acc + >>= wrap + in + let* () = + Assert.proto_error_with_info + ~loc:__LOC__ + e + "Negative length for pending list prefix" + in + (* Update with wrong address *) + let* _ctx, nonce = Raw_context.increment_origination_nonce ctx |> wrap in + let* address = + Zk_rollup_repr.Address.from_nonce (Origination_nonce.incr nonce) |> wrap + in + let*! e = + Zk_rollup_storage.update ctx address ~pending_to_drop:1 ~new_account:acc + >>= wrap + in + Assert.proto_error_with_info + ~loc:__LOC__ + e + "Storage error (fatal internal error)" end let tests = @@ -228,4 +386,14 @@ let tests = "pending_list_append errors" `Quick Raw_context_tests.pending_list_append_errors; + Tztest.tztest "pending_list_get" `Quick Raw_context_tests.pending_list_get; + Tztest.tztest + "pending_list_get errors" + `Quick + Raw_context_tests.pending_list_errors; + Tztest.tztest "test_update" `Quick Raw_context_tests.test_update; + Tztest.tztest + "test_update errors" + `Quick + Raw_context_tests.test_update_errors; ] diff --git a/src/proto_alpha/lib_protocol/validate.ml b/src/proto_alpha/lib_protocol/validate.ml index 98ba4f7520b1f5d1bbd0fa8fb9006e5355c3b2e4..f9a61d13a973111b41c92a72ff3a1663abbfa323 100644 --- a/src/proto_alpha/lib_protocol/validate.ml +++ b/src/proto_alpha/lib_protocol/validate.ml @@ -2441,7 +2441,7 @@ module Manager = struct assert_sc_rollup_feature_enabled vi | Dal_publish_slot_header {slot_header} -> Dal_apply.validate_publish_slot_header vi.ctxt slot_header - | Zk_rollup_origination _ | Zk_rollup_publish _ -> + | Zk_rollup_origination _ | Zk_rollup_publish _ | Zk_rollup_update _ -> assert_zk_rollup_feature_enabled vi in (* Gas should no longer be consumed below this point, because it diff --git a/src/proto_alpha/lib_protocol/zk_rollup_account_repr.ml b/src/proto_alpha/lib_protocol/zk_rollup_account_repr.ml index 7e04917bdb1e3ad8d6a3b9416ffd29ce9514dcdb..18d96d4a516b3a299fdf7f8e85115819b59c42aa 100644 --- a/src/proto_alpha/lib_protocol/zk_rollup_account_repr.ml +++ b/src/proto_alpha/lib_protocol/zk_rollup_account_repr.ml @@ -28,7 +28,7 @@ module SMap = Map.Make (String) type static = { public_parameters : Plonk.public_parameters; state_length : int; - circuits_info : bool SMap.t; + circuits_info : [`Public | `Private | `Fee] SMap.t; nb_ops : int; } @@ -40,8 +40,39 @@ type dynamic = { type t = {static : static; dynamic : dynamic} -let circuits_info_encoding : bool SMap.t Data_encoding.t = +let circuits_info_encoding : [`Public | `Private | `Fee] SMap.t Data_encoding.t + = let open Data_encoding in + let variant_encoding = + let public_tag, public_encoding = (0, obj1 @@ req "public" unit) in + let private_tag, private_encoding = (1, obj1 @@ req "private" unit) in + let fee_tag, fee_encoding = (2, obj1 @@ req "fee" unit) in + matching + (function + | `Public -> matched public_tag public_encoding () + | `Private -> matched private_tag private_encoding () + | `Fee -> matched fee_tag fee_encoding ()) + [ + case + ~title:"Public" + (Tag public_tag) + public_encoding + (function `Public -> Some () | _ -> None) + (fun () -> `Public); + case + ~title:"Private" + (Tag private_tag) + private_encoding + (function `Private -> Some () | _ -> None) + (fun () -> `Private); + case + ~title:"Fee" + (Tag fee_tag) + fee_encoding + (function `Fee -> Some () | _ -> None) + (fun () -> `Fee); + ] + in conv_with_guard (fun m -> List.of_seq @@ SMap.to_seq m) (fun l -> @@ -51,17 +82,11 @@ let circuits_info_encoding : bool SMap.t Data_encoding.t = Compare.List_length_with.(l <> SMap.cardinal m) then Error "Zk_rollup_origination: circuits_info has duplicated keys" else Ok m) - (list (tup2 string bool)) + (list (tup2 string variant_encoding)) let encoding = let open Data_encoding in let static_encoding = - let circuits_info_encoding = - conv - SMap.bindings - (fun l -> SMap.of_seq @@ List.to_seq l) - (list (tup2 string bool)) - in conv (fun {public_parameters; state_length; circuits_info; nb_ops} -> (public_parameters, state_length, circuits_info, nb_ops)) diff --git a/src/proto_alpha/lib_protocol/zk_rollup_account_repr.mli b/src/proto_alpha/lib_protocol/zk_rollup_account_repr.mli index 7a87437c22d456ee3fccf23744d891714c277d9b..57c133098a1a8cdb9be61f83e66129db14f07a44 100644 --- a/src/proto_alpha/lib_protocol/zk_rollup_account_repr.mli +++ b/src/proto_alpha/lib_protocol/zk_rollup_account_repr.mli @@ -34,9 +34,8 @@ type static = { (** Input to the Plonk verifier that are fixed once the circuits are decided. *) state_length : int; (** Number of scalars in the state. *) - circuits_info : bool SMap.t; - (** Circuit names, alongside a boolean flag indicating - if they can be used for private ops. *) + circuits_info : [`Public | `Private | `Fee] SMap.t; + (** Circuit names, alongside a tag indicating its kind. *) nb_ops : int; (** Valid op codes of L2 operations must be in \[0, nb_ops) *) } @@ -59,4 +58,4 @@ val encoding : t Data_encoding.t (* Encoding for the [circuits_info] field. Checks that keys are not duplicated in serialized representation. *) -val circuits_info_encoding : bool SMap.t Data_encoding.t +val circuits_info_encoding : [`Public | `Private | `Fee] SMap.t Data_encoding.t diff --git a/src/proto_alpha/lib_protocol/zk_rollup_apply.ml b/src/proto_alpha/lib_protocol/zk_rollup_apply.ml index c2ab9534c372d1a6d4670dd41fe3f3bf608e596e..4ab36d81b59c1ddc4bb616b4d8f05be728bbc51a 100644 --- a/src/proto_alpha/lib_protocol/zk_rollup_apply.ml +++ b/src/proto_alpha/lib_protocol/zk_rollup_apply.ml @@ -97,7 +97,6 @@ let parse_ticket ~ticketer ~contents ~ty ctxt = let publish ~ctxt_before_op ~ctxt ~zk_rollup ~l2_ops = let open Lwt_result_syntax in let*? () = assert_feature_enabled ctxt in - let open Zk_rollup.Operation in (* Deposits (i.e. L2 operations with a positive price) cannot be published through an external operation *) @@ -236,3 +235,257 @@ let transaction_to_zk_rollup ~ctxt ~parameters_ty ~parameters ~dst_rollup ~since })) in (ctxt, result, []) + +(* + A ZKRU Update will set a new ZKRU state if the proof sent in the payload + is verified. In order to verify this proof, the protocol needs to + compute the "public inputs" expected by the Plonk circuits that define + a given ZKRU. + The proof's public inputs have to be collected by the protocol, as some of + them will be passed in the operation's payload, but some must be computed + by the protocol (e.g. the current L2 state). + These public inputs will be collected as a string map linking + the circuit identifier to a list of inputs for it (as a circuit might have + been used several times in a proof). + As explained in the documentation, circuits in ZKRUs will be grouped into + three categories: pending (public) operations, private batches and + fee circuit. + Each of these expects a different set of public inputs. For this reason, + the collection of circuit inputs will be collected in three separate steps. +*) + +module SMap = Map.Make (String) + +(* Helper function to collect inputs *) +let insert s x = + SMap.update s (function None -> Some [x] | Some l -> Some (x :: l)) + +(* Traverse the list of pending L2 operations paired with their corresponding + inputs sent in the [Update] computing the full set of inputs for each of + them. + Collect the L2 fees of all L2 operations, and the list of boolean flags + determining whether each L2 operation will trigger an exit. +*) +let collect_pending_ops_inputs ~zk_rollup ~account ~rev_pi_map + ~pending_ops_and_pis = + let open Lwt_result_syntax in + let open Zk_rollup.Update in + let open Zk_rollup.Account in + let* rev_pi_map, new_state, fees, rev_exit_validites = + List.fold_left_es + (fun (rev_pi_map, old_state, fees, rev_exit_validites) + ((l2_op, _ticket_hash_opt), (name, (sent_pi : op_pi))) -> + let new_state = sent_pi.new_state in + let*? () = + error_unless + Compare.Int.(Array.length new_state = account.static.state_length) + Zk_rollup.Errors.Inconsistent_state_update + in + let pi = + Zk_rollup.Circuit_public_inputs.( + Pending_op + { + old_state; + new_state; + fee = sent_pi.fee; + exit_validity = sent_pi.exit_validity; + zk_rollup; + l2_op; + }) + in + let rev_pi_map = + insert + name + (Zk_rollup.Circuit_public_inputs.to_scalar_array pi) + rev_pi_map + in + return + ( rev_pi_map, + new_state, + Bls.Primitive.Fr.add fees sent_pi.fee, + sent_pi.exit_validity :: rev_exit_validites )) + (rev_pi_map, account.dynamic.state, Bls.Primitive.Fr.zero, []) + pending_ops_and_pis + in + return (rev_pi_map, new_state, fees, List.rev rev_exit_validites) + +(* Traverse the partial inputs for the batches of private operations + that the [update] claims to process, computing the full set of inputs. + Check that all circuit identifiers used here are allowed to be used for + private operations and collect the L2 fees. *) +let collect_pivate_batch_inputs ~zk_rollup ~account ~rev_pi_map ~update + ~prev_state ~fees = + let open Lwt_result_syntax in + let open Zk_rollup.Update in + let open Zk_rollup.Account in + let is_private = function Some `Private -> true | _ -> false in + List.fold_left_es + (fun (rev_pi_map, old_state, fees) (name, (sent_pi : private_inner_pi)) -> + let*? () = + error_unless + (is_private + (Zk_rollup.Account.SMap.find name account.static.circuits_info)) + Zk_rollup.Errors.Invalid_circuit + in + let new_state = sent_pi.new_state in + let*? () = + error_unless + Compare.Int.(Array.length new_state = account.static.state_length) + Zk_rollup.Errors.Inconsistent_state_update + in + let pi = + Zk_rollup.Circuit_public_inputs.( + Private_batch {old_state; new_state; fees = sent_pi.fees; zk_rollup}) + in + let rev_pi_map = + insert + name + (Zk_rollup.Circuit_public_inputs.to_scalar_array pi) + rev_pi_map + in + + return (rev_pi_map, new_state, Bls.Primitive.Fr.add fees sent_pi.fees)) + (rev_pi_map, prev_state, fees) + update.private_pis + +let collect_fee_inputs ~prev_state ~update ~fees ~rev_pi_map = + let open Zk_rollup.Update in + let old_state = prev_state in + let new_state = update.fee_pi.new_state in + let pi = Zk_rollup.Circuit_public_inputs.(Fee {old_state; new_state; fees}) in + let rev_pi_map = + insert "fee" (Zk_rollup.Circuit_public_inputs.to_scalar_array pi) rev_pi_map + in + (rev_pi_map, new_state) + +(* Collect and validate the public inputs for the verification *) +let collect_inputs ~zk_rollup ~account ~rev_pi_map ~pending_ops_and_pis ~update + = + let open Lwt_result_syntax in + (* Collect the inputs for the pending L2 ops *) + let* rev_pi_map, new_state, fees, exit_validities = + collect_pending_ops_inputs + ~zk_rollup + ~account + ~rev_pi_map + ~pending_ops_and_pis + in + (* Collect the inputs for private batches of L2 ops *) + let* rev_pi_map, new_state, fees = + collect_pivate_batch_inputs + ~zk_rollup + ~account + ~rev_pi_map + ~update + ~prev_state:new_state + ~fees + in + (* Collect the inputs for the fee circuit, always identified as "fee" *) + let rev_pi_map, new_state = + collect_fee_inputs ~prev_state:new_state ~update ~fees ~rev_pi_map + in + let pi_map = SMap.map List.rev rev_pi_map in + return (pi_map, exit_validities, new_state) + +(* Perform the exits corresponding to the processed public l2 operations *) +let perform_exits ctxt exits = + let open Lwt_result_syntax in + List.fold_left_es + (fun (ctxt, storage_diff) ((op, ticket_hash_opt), exit_validity) -> + let open Zk_rollup.Operation in + match ticket_hash_opt with + | None -> + let*? () = + error_unless + Compare.Z.(Z.zero = op.price.amount) + Zk_rollup.Errors.Invalid_deposit_amount + in + return (ctxt, storage_diff) + | Some receiver_ticket_hash -> + if exit_validity then + let*? amount = + Option.value_e + ~error: + (Error_monad.trace_of_error + Zk_rollup.Errors.Invalid_deposit_amount) + (Ticket_amount.of_zint (Z.abs @@ op.price.amount)) + in + let* ctxt, diff = + Tx_rollup_ticket.transfer_ticket_with_hashes + ctxt + ~src_hash:op.price.id + ~dst_hash:receiver_ticket_hash + amount + in + return (ctxt, Z.add diff storage_diff) + else return (ctxt, storage_diff)) + (ctxt, Z.zero) + exits + +let update ~ctxt_before_op ~ctxt ~zk_rollup ~update = + let open Lwt_result_syntax in + let open Zk_rollup.Update in + let*? () = assert_feature_enabled ctxt in + let rev_pi_map = SMap.empty in + let* ctxt, account = Zk_rollup.account ctxt zk_rollup in + let update_public_length = List.length update.pending_pis in + let* ctxt, pending_list_length = + Zk_rollup.get_pending_length ctxt zk_rollup + in + let min_pending_to_process = + Constants.zk_rollup_min_pending_to_process ctxt + in + (* The number of pending operations processed by an update must be at least + [min(pending_list_length, min_pending_to_process)] and at most + [pending_list_length].*) + let*? () = + error_when + Compare.Int.( + update_public_length < pending_list_length + && update_public_length < min_pending_to_process) + Zk_rollup.Errors.Pending_bound + in + let* ctxt, pending_ops = + Zk_rollup.get_prefix ctxt zk_rollup update_public_length + in + (* It's safe to use [combine_drop], as at this point both lists will have the + same length. *) + let pending_ops_and_pis = List.combine_drop pending_ops update.pending_pis in + (* Collect the inputs for the verification *) + let* pi_map, exit_validities, new_state = + collect_inputs ~zk_rollup ~account ~rev_pi_map ~pending_ops_and_pis ~update + in + (* Run the verification of the Plonk proof *) + let verified = + Plonk.verify_multi_circuits + account.static.public_parameters + ~public_inputs:(SMap.bindings pi_map) + update.proof + in + let*? () = error_unless verified Zk_rollup.Errors.Invalid_verification in + (* Update the ZKRU storage with the new state and dropping the processed + public L2 operations from the pending list *) + let* ctxt = + Zk_rollup.update + ctxt + zk_rollup + ~pending_to_drop:update_public_length + ~new_account: + {account with dynamic = {account.dynamic with state = new_state}} + in + (* Perform exits of processed public L2 operations *) + let exits = List.combine_drop pending_ops exit_validities in + let* ctxt, exits_paid_storage_size_diff = perform_exits ctxt exits in + + (* TODO https://gitlab.com/tezos/tezos/-/issues/3544 + Carbonate ZKRU operations *) + let consumed_gas = Gas.consumed ~since:ctxt_before_op ~until:ctxt in + let result = + Apply_results.Zk_rollup_update_result + { + balance_updates = []; + consumed_gas; + paid_storage_size_diff = exits_paid_storage_size_diff; + } + in + return (ctxt, result, []) diff --git a/src/proto_alpha/lib_protocol/zk_rollup_apply.mli b/src/proto_alpha/lib_protocol/zk_rollup_apply.mli index 81b27dffd0efb7b646edba0bb1d18656c3ceaadd..7e01854acc3ac8d8ec83a823377e08007c692da7 100644 --- a/src/proto_alpha/lib_protocol/zk_rollup_apply.mli +++ b/src/proto_alpha/lib_protocol/zk_rollup_apply.mli @@ -122,7 +122,7 @@ val originate : ctxt_before_op:t -> ctxt:t -> public_parameters:Plonk.public_parameters -> - circuits_info:bool Zk_rollup.Account.SMap.t -> + circuits_info:[`Public | `Private | `Fee] Zk_rollup.Account.SMap.t -> init_state:Zk_rollup.State.t -> nb_ops:int -> (t @@ -233,3 +233,60 @@ val transaction_to_zk_rollup : * Script_typed_ir.packed_internal_operation list) tzresult Lwt.t + +(** [update ~ctxt_before_op ~ctxt ~zk_rollup ~update ~source_contract] + applies an [update] to [zk_rollup]. + + A ZKRU update will verify three sorts of ZK circuits: + {ul + {li Public operation circuits, that handle a single L2 operation + from the pending list.} + {li Private batch circuits, that handle a batch of private L2 + operations.} + {li Fee circuit, which credits the ZKRU operator with all the aggregated + fees from the update.} + } + + The [update] provides some inputs required to perform this verification, + alongside the proof. See {!Zk_rollup_update_repr}. + + If the verification is successful, the [zk_rollup]'s state is updated, + a prefix of its pending list is dropped and the exits from the ZKRU are + performed. + + May fail with: + {ul + {li [Zk_rollup_feature_disabled] if the ZKRU feature flag is not + activated. + } + {li [Zk_rollup.Errors.Pending_bound] if the [update] processes fewer + public operation than allowed. + } + {li [Zk_rollup.Errors.Inconsistent_state_update] if the [update] declares + a new state of incorrect length. + } + {li [Zk_rollup.Errors.Invalid_circuit] if a public operation circuit is + ran as private. + } + {li [Zk_rollup.Errors.Invalid_verification] if the PlonK verification + fails. + } + {li [Zk_rollup.Errors.Invalid_deposit_amount] if an L2 operation without + a corresponding ticket in the pending list has a non-zero price. + } + {li [Zk_rollup_storage.Zk_rollup_pending_list_too_short] + if the [update] tries to process more public operations than those in + the pending list. + } + } +*) +val update : + ctxt_before_op:t -> + ctxt:t -> + zk_rollup:Zk_rollup.t -> + update:Zk_rollup.Update.t -> + (t + * Kind.zk_rollup_update Apply_results.successful_manager_operation_result + * Script_typed_ir.packed_internal_operation list) + tzresult + Lwt.t diff --git a/src/proto_alpha/lib_protocol/zk_rollup_circuit_public_inputs_repr.ml b/src/proto_alpha/lib_protocol/zk_rollup_circuit_public_inputs_repr.ml new file mode 100644 index 0000000000000000000000000000000000000000..6ed68ffd02a519309d3b25b1adbafdf59547ad29 --- /dev/null +++ b/src/proto_alpha/lib_protocol/zk_rollup_circuit_public_inputs_repr.ml @@ -0,0 +1,73 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +type pending_op_public_inputs = { + old_state : Zk_rollup_state_repr.t; + new_state : Zk_rollup_state_repr.t; + fee : Zk_rollup_scalar.t; + exit_validity : bool; + zk_rollup : Zk_rollup_repr.t; + l2_op : Zk_rollup_operation_repr.t; +} + +type private_batch_public_inputs = { + old_state : Zk_rollup_state_repr.t; + new_state : Zk_rollup_state_repr.t; + fees : Zk_rollup_scalar.t; + zk_rollup : Zk_rollup_repr.t; +} + +type fee_public_inputs = { + old_state : Zk_rollup_state_repr.t; + new_state : Zk_rollup_state_repr.t; + fees : Zk_rollup_scalar.t; +} + +type t = + | Pending_op of pending_op_public_inputs + | Private_batch of private_batch_public_inputs + | Fee of fee_public_inputs + +let bool_to_scalar b = + if b then Zk_rollup_scalar.of_z Z.one else Zk_rollup_scalar.of_z Z.zero + +let to_scalar_array = function + | Pending_op {old_state; new_state; fee; exit_validity; zk_rollup; l2_op} -> + Array.concat + [ + old_state; + new_state; + [| + fee; + bool_to_scalar exit_validity; + Zk_rollup_repr.to_scalar zk_rollup; + |]; + Zk_rollup_operation_repr.to_scalar_array l2_op; + ] + | Private_batch {old_state; new_state; fees; zk_rollup} -> + Array.concat + [old_state; new_state; [|fees; Zk_rollup_repr.to_scalar zk_rollup|]] + | Fee {old_state; new_state; fees} -> + Array.concat [old_state; new_state; [|fees|]] diff --git a/src/proto_alpha/lib_protocol/zk_rollup_circuit_public_inputs_repr.mli b/src/proto_alpha/lib_protocol/zk_rollup_circuit_public_inputs_repr.mli new file mode 100644 index 0000000000000000000000000000000000000000..74985106c63f638d0fe64c132c2821a71972397c --- /dev/null +++ b/src/proto_alpha/lib_protocol/zk_rollup_circuit_public_inputs_repr.mli @@ -0,0 +1,67 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +(** + Abstraction layer for the public inputs to the ZKRU aPlonk circuits. + + As explained in the documentation, circuits in ZKRUs will be grouped into + three categories: pending (public) operations, private batches and + fee circuit. Each of these expects a different set of public inputs. +*) + +(** Public inputs expected by circuits that handle single public + L2 operations. *) +type pending_op_public_inputs = { + old_state : Zk_rollup_state_repr.t; + new_state : Zk_rollup_state_repr.t; + fee : Zk_rollup_scalar.t; + exit_validity : bool; + zk_rollup : Zk_rollup_repr.t; + l2_op : Zk_rollup_operation_repr.t; +} + +(** Public inputs expected by circuits that handle a batch of private + L2 operations. *) +type private_batch_public_inputs = { + old_state : Zk_rollup_state_repr.t; + new_state : Zk_rollup_state_repr.t; + fees : Zk_rollup_scalar.t; + zk_rollup : Zk_rollup_repr.t; +} + +(** Public inputs expected by the circuit that handles the L2 fees. *) +type fee_public_inputs = { + old_state : Zk_rollup_state_repr.t; + new_state : Zk_rollup_state_repr.t; + fees : Zk_rollup_scalar.t; +} + +type t = + | Pending_op of pending_op_public_inputs + | Private_batch of private_batch_public_inputs + | Fee of fee_public_inputs + +(** Conversion to the type the aPlonk verifier expects. *) +val to_scalar_array : t -> Zk_rollup_scalar.t array diff --git a/src/proto_alpha/lib_protocol/zk_rollup_errors.ml b/src/proto_alpha/lib_protocol/zk_rollup_errors.ml index 9ed216548c8a482179b24863c02f941dc4c9e5cd..38bbcf2283f580e5c52fa2d1d26e99fc3336949c 100644 --- a/src/proto_alpha/lib_protocol/zk_rollup_errors.ml +++ b/src/proto_alpha/lib_protocol/zk_rollup_errors.ml @@ -32,6 +32,10 @@ type error += payload_size : Saturation_repr.may_saturate Saturation_repr.t; limit : int; } + | Invalid_verification + | Invalid_circuit + | Inconsistent_state_update + | Pending_bound let () = register_error_kind @@ -88,4 +92,45 @@ let () = Some (payload_size, limit) | _ -> None) (fun (payload_size, limit) -> - Ticket_payload_size_limit_exceeded {payload_size; limit}) + Ticket_payload_size_limit_exceeded {payload_size; limit}) ; + register_error_kind + `Temporary + ~id:"operation.zk_rollup_failed_verification" + ~title:"Zk_rollup_update: failed verification" + ~description:"Zk_rollup_update: failed verification" + ~pp:(fun ppf () -> Format.fprintf ppf "The proof verification failed") + Data_encoding.empty + (function Invalid_verification -> Some () | _ -> None) + (fun () -> Invalid_verification) ; + register_error_kind + `Permanent + ~id:"operation.zk_rollup_invalid_circuit" + ~title:"Zk_rollup_update: invalid circuit" + ~description:"Zk_rollup_update: invalid circuit" + ~pp:(fun ppf () -> + Format.fprintf ppf "Invalid circuit in proof verification") + Data_encoding.empty + (function Invalid_circuit -> Some () | _ -> None) + (fun () -> Invalid_circuit) ; + register_error_kind + `Permanent + ~id:"operation.zk_rollup_inconsistent_state_update" + ~title:"Zk_rollup_update: inconsistent state update" + ~description:"Zk_rollup_update: new state is of incorrect size" + ~pp:(fun ppf () -> + Format.fprintf ppf "Zk_rollup_update: new state is of incorrect size") + Data_encoding.empty + (function Inconsistent_state_update -> Some () | _ -> None) + (fun () -> Inconsistent_state_update) ; + register_error_kind + `Temporary + ~id:"operation.zk_rollup_pending_bound" + ~title:"Zk_rollup_update: update with fewer pending ops than allowed" + ~description:"Zk_rollup_update: update with fewer pending ops than allowed" + ~pp:(fun ppf () -> + Format.fprintf + ppf + "Zk_rollup_update: update with fewer pending ops than allowed") + Data_encoding.empty + (function Pending_bound -> Some () | _ -> None) + (fun () -> Pending_bound) diff --git a/src/proto_alpha/lib_protocol/zk_rollup_storage.ml b/src/proto_alpha/lib_protocol/zk_rollup_storage.ml index a6ffc7d7c66fac4c276dbcca61ba2e7ed401c367..276827637b2eac493d627be54b3dbcf803150938 100644 --- a/src/proto_alpha/lib_protocol/zk_rollup_storage.ml +++ b/src/proto_alpha/lib_protocol/zk_rollup_storage.ml @@ -26,6 +26,8 @@ type error += | Zk_rollup_does_not_exist of Zk_rollup_repr.t | Zk_rollup_invalid_op_code of int + | Zk_rollup_pending_list_too_short + | Zk_rollup_negative_length let () = register_error_kind @@ -47,7 +49,29 @@ let () = Format.fprintf ppf "Op code %d is not valid for this ZK Rollup" oc) Data_encoding.(obj1 (req "op_code" int31)) (function Zk_rollup_invalid_op_code oc -> Some oc | _ -> None) - (fun oc -> Zk_rollup_invalid_op_code oc) + (fun oc -> Zk_rollup_invalid_op_code oc) ; + register_error_kind + `Temporary + ~id:"Zk_rollup_pending_list_too_short" + ~title:"Pending list is too short" + ~description:"Pending list is too short" + Data_encoding.unit + (function Zk_rollup_pending_list_too_short -> Some () | _ -> None) + (fun () -> Zk_rollup_pending_list_too_short) ; + register_error_kind + `Permanent + ~id:"Zk_rollup_negative_length" + ~title:"Negative length for pending list prefix" + ~description:"Negative length for pending list prefix" + Data_encoding.unit + (function Zk_rollup_negative_length -> Some () | _ -> None) + (fun () -> Zk_rollup_negative_length) + +let account = Storage.Zk_rollup.Account.get + +let pending_list = Storage.Zk_rollup.Pending_list.get + +let pending_op ctxt id = Storage.Zk_rollup.Pending_operation.get (ctxt, id) let originate ctxt static ~init_state = let open Lwt_result_syntax in @@ -83,7 +107,7 @@ let add_to_pending ctxt rollup ops = let open Lwt_result_syntax in let open Zk_rollup_repr in let open Zk_rollup_operation_repr in - let* ctxt, acc = Storage.Zk_rollup.Account.get ctxt rollup in + let* ctxt, acc = account ctxt rollup in let*? () = List.iter_e (fun (op, _ticket_hash_opt) -> @@ -159,6 +183,135 @@ let add_to_pending ctxt rollup ops = let* ctxt, _diff_pl = Storage.Zk_rollup.Pending_list.update ctxt rollup pl in return (ctxt, l2_operations_storage_space_to_pay) +let pending_length = + let open Zk_rollup_repr in + function Empty _ -> 0 | Pending {length; _} -> length + +let head = + let open Zk_rollup_repr in + function + | Empty _ -> error Zk_rollup_pending_list_too_short + | Pending {next_index; length} -> + Result_syntax.return Int64.(sub next_index (of_int length)) + +let next_index = + let open Zk_rollup_repr in + function + | Empty {next_index} -> next_index | Pending {next_index; _} -> next_index + +let get_pending_length ctxt rollup = + let open Lwt_result_syntax in + let* ctxt, pl = pending_list ctxt rollup in + return (ctxt, pending_length pl) + +(** Same as [Tezos_stdlib.Utils.fold_n_times] but with Lwt and Error monad *) +let fold_n_times_es : + when_negative:error trace -> + int -> + ('a -> 'a tzresult Lwt.t) -> + 'a -> + 'a tzresult Lwt.t = + fun ~when_negative n f e -> + let open Lwt_result_syntax in + if Compare.Int.(n < 0) then fail when_negative + else + let rec go acc = function + | 0 -> return acc + | n -> + let* acc = f acc in + (go [@ocaml.tailcall]) acc (n - 1) + in + go e n + +let get_prefix ctxt rollup n = + let open Lwt_result_syntax in + if Compare.Int.(n = 0) then return (ctxt, []) + else + let* ctxt, pl = pending_list ctxt rollup in + let pl_length = pending_length pl in + let*? () = + error_when Compare.Int.(n > pl_length) Zk_rollup_pending_list_too_short + in + let*? hd = head pl in + let* ctxt, ops, _i = + (* Get the l2 ops corresponding to indeces [hd + n - 1 .. hd], + so that the accumulated list is in the right order *) + fold_n_times_es + ~when_negative:(Error_monad.trace_of_error Zk_rollup_negative_length) + n + (fun (ctxt, ops, i) -> + let* ctxt, op = pending_op ctxt rollup i in + return (ctxt, op :: ops, Int64.pred i)) + (ctxt, [], Int64.(sub (add hd (of_int n)) 1L)) + in + return (ctxt, ops) + +let update ctxt rollup ~pending_to_drop ~new_account = + let open Lwt_result_syntax in + let open Zk_rollup_repr in + let open Zk_rollup_account_repr in + let* ctxt, pl = pending_list ctxt rollup in + let* ctxt, acc = account ctxt rollup in + let pl_length = pending_length pl in + let*? () = + error_when + Compare.Int.(pending_to_drop > pl_length) + Zk_rollup_pending_list_too_short + in + let next_index = next_index pl in + (* Drop the indeces from [head] to [head + pending_to_drop - 1] + from the storage of L2 operations. *) + let* ctxt, freed = + match head pl with + | Error _e -> + (* If the pending list is empty, then [pending_to_drop] must be 0. *) + return (ctxt, 0) + | Ok head -> + let* ctxt, freed, _i = + fold_n_times_es + ~when_negative: + (Error_monad.trace_of_error Zk_rollup_negative_length) + pending_to_drop + (fun (ctxt, freed, i) -> + let* ctxt, new_freed, _bound = + Storage.Zk_rollup.Pending_operation.remove (ctxt, rollup) i + in + return (ctxt, freed + new_freed, Int64.succ i)) + (ctxt, 0, head) + in + return (ctxt, freed) + in + (* Subtract the bytes freed by removing pending operations from + acc.dynamic.used_l2_operations_storage_space, and update + [new_account]. + *) + let used_l2_operations_storage_space = + Z.(sub acc.dynamic.used_l2_operations_storage_space (Z.of_int freed)) + in + let new_account = + { + new_account with + dynamic = + { + state = new_account.dynamic.state; + paid_l2_operations_storage_space = + new_account.dynamic.paid_l2_operations_storage_space; + used_l2_operations_storage_space; + }; + } + in + let* ctxt, _diff_acc = + Storage.Zk_rollup.Account.update ctxt rollup new_account + in + (* Update the pending list descriptor *) + let pl_length = pl_length - pending_to_drop in + let pl = + if Compare.Int.(pl_length = 0) then Empty {next_index} + else Pending {next_index; length = pl_length} + in + let* ctxt, _diff_pl = Storage.Zk_rollup.Pending_list.update ctxt rollup pl in + return ctxt + let assert_exist ctxt rollup = let open Lwt_result_syntax in let* ctxt, exists = Storage.Zk_rollup.Account.mem ctxt rollup in diff --git a/src/proto_alpha/lib_protocol/zk_rollup_storage.mli b/src/proto_alpha/lib_protocol/zk_rollup_storage.mli index 74e18a914a74e66130376e3ada4e2fa058537246..7f4c06e42e8a7c7c257d93a038b0f6396be8c452 100644 --- a/src/proto_alpha/lib_protocol/zk_rollup_storage.mli +++ b/src/proto_alpha/lib_protocol/zk_rollup_storage.mli @@ -31,6 +31,37 @@ type error += | Zk_rollup_invalid_op_code of int (** Emitted when trying to add to the pending list and operation with an invalid op code. *) + | Zk_rollup_pending_list_too_short + (** Emitted when trying to process more public operations than + those available in the pending list. *) + +(** [account context rollup] fetches the ZK [rollup]'s account from the + storage. +*) +val account : + Raw_context.t -> + Zk_rollup_repr.t -> + (Raw_context.t * Zk_rollup_account_repr.t) tzresult Lwt.t + +(* [pending_list context rollup] fetches the ZK [rollup]'s + pending list description from the storage. + See {! Zk_rollup_repr.pending_list}. *) +val pending_list : + Raw_context.t -> + Zk_rollup_repr.t -> + (Raw_context.t * Zk_rollup_repr.pending_list) tzresult Lwt.t + +(* [pending_op context rollup i] fetches the [i]th L2 operation from + ZK [rollup]'s pending list, alongside an optional ticket hash + to perform an exit (see {!Zk_rollup_apply} for more details). +*) +val pending_op : + Raw_context.t -> + Zk_rollup_repr.t -> + int64 -> + (Raw_context.t * (Zk_rollup_operation_repr.t * Ticket_hash_repr.t option)) + tzresult + Lwt.t (** [originate context static ~init_state] produces an address [a] for a ZK rollup storage using the [origination_nonce] from @@ -64,6 +95,51 @@ val add_to_pending : (Zk_rollup_operation_repr.t * Ticket_hash_repr.t option) list -> (Raw_context.t * Z.t) tzresult Lwt.t +(** [get_pending_length context rollup] returns the length of a + ZK [rollup]'s pending list. +*) +val get_pending_length : + Raw_context.t -> Zk_rollup_repr.t -> (Raw_context.t * int) tzresult Lwt.t + +(** [get_prefix context rollup n] returns the prefix of length [n] + of the [rollup]'s pending list. + + May fail with: + {ul + {li [Zk_rollup_pending_list_too_short] if [n] is greater than + the length of the pending list.} + {li [Zk_rollup_negative_length] if [n] is negative.} + } +*) +val get_prefix : + Raw_context.t -> + Zk_rollup_repr.t -> + int -> + (Raw_context.t + * (Zk_rollup_operation_repr.t * Ticket_hash_repr.t option) list) + tzresult + Lwt.t + +(** [update context rollup ~pending_to_drop ~new_account] sets the + [rollup]'s account to [new_account]. Additionally, it removes + the first [pending_to_drop] entries from the [rollup]'s pending + list. + Returns the new context. + + May fail with: + {ul + {li [Zk_rollup_pending_list_too_short] if [pending_to_drop] is + greater than the length of the pending list.} + {li [Zk_rollup_negative_length] if [pending_to_drop] is negative.} + } +*) +val update : + Raw_context.t -> + Zk_rollup_repr.t -> + pending_to_drop:int -> + new_account:Zk_rollup_account_repr.t -> + Raw_context.t tzresult Lwt.t + (** [assert_exist context rollup] asserts that [rollup] has been initialized. Returns the new context. diff --git a/src/proto_alpha/lib_protocol/zk_rollup_update_repr.ml b/src/proto_alpha/lib_protocol/zk_rollup_update_repr.ml new file mode 100644 index 0000000000000000000000000000000000000000..0f81f441a20aeb815b5e5909cb72415aebadb7a6 --- /dev/null +++ b/src/proto_alpha/lib_protocol/zk_rollup_update_repr.ml @@ -0,0 +1,84 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +type op_pi = { + new_state : Zk_rollup_state_repr.t; + fee : Zk_rollup_scalar.t; + exit_validity : bool; +} + +type private_inner_pi = { + new_state : Zk_rollup_state_repr.t; + fees : Zk_rollup_scalar.t; +} + +type fee_pi = {new_state : Zk_rollup_state_repr.t} + +(* Data sent to an update operation *) +type t = { + pending_pis : (string * op_pi) list; + private_pis : (string * private_inner_pi) list; + fee_pi : fee_pi; + proof : Plonk.proof; +} + +let op_pi_encoding : op_pi Data_encoding.t = + Data_encoding.( + conv + (fun {new_state; fee; exit_validity} -> (new_state, fee, exit_validity)) + (fun (new_state, fee, exit_validity) -> {new_state; fee; exit_validity}) + (obj3 + (req "new_state" Zk_rollup_state_repr.encoding) + (req "fee" Plonk.scalar_encoding) + (req "exit_validity" bool))) + +let private_inner_pi_encoding : private_inner_pi Data_encoding.t = + Data_encoding.( + conv + (fun ({new_state; fees} : private_inner_pi) -> (new_state, fees)) + (fun (new_state, fees) -> {new_state; fees}) + (obj2 + (req "new_state" Zk_rollup_state_repr.encoding) + (req "fee" Plonk.scalar_encoding))) + +let fee_pi_encoding : fee_pi Data_encoding.t = + Data_encoding.( + conv + (fun {new_state} -> new_state) + (fun new_state -> {new_state}) + (obj1 (req "new_state" Zk_rollup_state_repr.encoding))) + +let encoding : t Data_encoding.t = + Data_encoding.( + conv + (fun {pending_pis; private_pis; fee_pi; proof} -> + (pending_pis, private_pis, fee_pi, proof)) + (fun (pending_pis, private_pis, fee_pi, proof) -> + {pending_pis; private_pis; fee_pi; proof}) + (obj4 + (req "pending_pis" (list @@ tup2 string op_pi_encoding)) + (req "private_pis" (list @@ tup2 string private_inner_pi_encoding)) + (req "fee_pi" fee_pi_encoding) + (req "proof" Plonk.proof_encoding))) diff --git a/src/proto_alpha/lib_protocol/zk_rollup_update_repr.mli b/src/proto_alpha/lib_protocol/zk_rollup_update_repr.mli new file mode 100644 index 0000000000000000000000000000000000000000..a8a2dfaee70de3303a9d972f88fca6015c71ed4b --- /dev/null +++ b/src/proto_alpha/lib_protocol/zk_rollup_update_repr.mli @@ -0,0 +1,60 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +(** Payload of a ZK Rollup update operation. + The operator only needs to send a subset of the public inputs + defined in {!Zk_rollup_circuit_public_inputs_repr}, the rest + is provided by the protocol. +*) + +(** Minimal subset of public inputs for the public L2 operations' circuits. *) +type op_pi = { + new_state : Zk_rollup_state_repr.t; + fee : Zk_rollup_scalar.t; + exit_validity : bool; +} + +(** Minimal subset of public inputs for the circuits for batches of + private L2 operations *) +type private_inner_pi = { + new_state : Zk_rollup_state_repr.t; + fees : Zk_rollup_scalar.t; +} + +(** Minimal subset of public inputs for the "fee" circuit. *) +type fee_pi = {new_state : Zk_rollup_state_repr.t} + +(** Payload of an update operation. + Includes the proof and the public inputs that are needed to verify it. + Each set of public inputs also carries the string that identifies the + circuit which they are for. *) +type t = { + pending_pis : (string * op_pi) list; + private_pis : (string * private_inner_pi) list; + fee_pi : fee_pi; + proof : Plonk.proof; +} + +val encoding : t Data_encoding.t