From 0eb9c373d0b6fbbda7ad034d3bf9fd86b776aace Mon Sep 17 00:00:00 2001 From: Felix Puscasu Date: Wed, 21 Aug 2024 15:53:45 +0100 Subject: [PATCH 1/2] Rollup-Node: Simple linearization of rollup node --- src/lib_smart_rollup_node/interpreter.ml | 2 +- .../lib_sc_rollup_node/fueled_pvm.ml | 54 ++++++++++--------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/lib_smart_rollup_node/interpreter.ml b/src/lib_smart_rollup_node/interpreter.ml index d867bf5c6829..e61b8f6450ec 100644 --- a/src/lib_smart_rollup_node/interpreter.ml +++ b/src/lib_smart_rollup_node/interpreter.ml @@ -129,6 +129,7 @@ let transition_pvm (module Plugin : Protocol_plugin_sig.PARTIAL) node_ctxt ctxt let* predecessor_state = state_of_head (module Plugin) node_ctxt ctxt predecessor in + let*! initial_tick = Plugin.Pvm.get_tick node_ctxt.kind predecessor_state in let* eval_result = Plugin.Pvm.Fueled.Free.eval_block_inbox ~fuel:(Fuel.Free.of_ticks 0L) @@ -144,7 +145,6 @@ let transition_pvm (module Plugin : Protocol_plugin_sig.PARTIAL) node_ctxt ctxt Delayed_write_monad.apply node_ctxt eval_result in let*! ctxt = Context.PVMState.set ctxt state in - let*! initial_tick = Plugin.Pvm.get_tick node_ctxt.kind predecessor_state in (* Produce events. *) let*! () = Interpreter_event.transitioned_pvm inbox_level state_hash tick num_messages diff --git a/src/proto_alpha/lib_sc_rollup_node/fueled_pvm.ml b/src/proto_alpha/lib_sc_rollup_node/fueled_pvm.ml index 61c74e584c21..bad555ed66ed 100644 --- a/src/proto_alpha/lib_sc_rollup_node/fueled_pvm.ml +++ b/src/proto_alpha/lib_sc_rollup_node/fueled_pvm.ml @@ -252,52 +252,54 @@ module Make_fueled (F : Fuel.S) : FUELED_PVM with type fuel = F.t = struct failing_ticks next_state) | Needs_reveal (Reveal_raw_data hash) -> ( - let* data = - get_reveal - ~dac_client:node_ctxt.dac_client - ~pre_images_endpoint:node_ctxt.config.pre_images_endpoint - ~data_dir:node_ctxt.data_dir - ~pvm_kind:node_ctxt.kind - reveal_map - hash - in - let*! next_state = PVM.set_input (Reveal (Raw_data data)) state in match F.consume F.one_tick_consumption fuel with | None -> abort state fuel current_tick | Some fuel -> + let* data = + get_reveal + ~dac_client:node_ctxt.dac_client + ~pre_images_endpoint:node_ctxt.config.pre_images_endpoint + ~data_dir:node_ctxt.data_dir + ~pvm_kind:node_ctxt.kind + reveal_map + hash + in + let*! next_state = PVM.set_input (Reveal (Raw_data data)) state in go fuel (Int64.succ current_tick) failing_ticks next_state) | Needs_reveal Reveal_metadata -> ( - let*! next_state = PVM.set_input (Reveal (Metadata metadata)) state in match F.consume F.one_tick_consumption fuel with | None -> abort state fuel current_tick | Some fuel -> + let*! next_state = + PVM.set_input (Reveal (Metadata metadata)) state + in go fuel (Int64.succ current_tick) failing_ticks next_state) | Needs_reveal (Request_dal_page page_id) -> ( - let* content_opt = - Dal_pages_request.page_content - constants.dal - ~inbox_level:(Int32.of_int level) - ~dal_activation_level - ~dal_attested_slots_validity_lag - node_ctxt - page_id - in - let*! next_state = - PVM.set_input (Reveal (Dal_page content_opt)) state - in match F.consume F.one_tick_consumption fuel with | None -> abort state fuel current_tick | Some fuel -> + let* content_opt = + Dal_pages_request.page_content + constants.dal + ~inbox_level:(Int32.of_int level) + ~dal_activation_level + ~dal_attested_slots_validity_lag + node_ctxt + page_id + in + let*! next_state = + PVM.set_input (Reveal (Dal_page content_opt)) state + in go fuel (Int64.succ current_tick) failing_ticks next_state) | Needs_reveal Reveal_dal_parameters -> ( (* TODO: https://gitlab.com/tezos/tezos/-/issues/6562 Consider supporting revealing of historical DAL parameters. *) - let*! next_state = - PVM.set_input (Reveal (Dal_parameters dal_parameters)) state - in match F.consume F.one_tick_consumption fuel with | None -> abort state fuel current_tick | Some fuel -> + let*! next_state = + PVM.set_input (Reveal (Dal_parameters dal_parameters)) state + in go fuel (Int64.succ current_tick) failing_ticks next_state) | Initial | First_after _ -> complete state fuel current_tick failing_ticks -- GitLab From a3bc78286e9eb2bf7eb7c0a16f19f80b7e53cb59 Mon Sep 17 00:00:00 2001 From: Felix Puscasu Date: Fri, 6 Sep 2024 11:42:35 +0100 Subject: [PATCH 2/2] Rollup-Node: Linearize eval_tick function --- .../rollup_node_errors.ml | 30 +++++++- .../lib_sc_rollup_node/fueled_pvm.ml | 72 +++++++++++-------- 2 files changed, 72 insertions(+), 30 deletions(-) diff --git a/src/lib_smart_rollup_node/rollup_node_errors.ml b/src/lib_smart_rollup_node/rollup_node_errors.ml index cc4f9772a273..d836a27bb1c3 100644 --- a/src/lib_smart_rollup_node/rollup_node_errors.ml +++ b/src/lib_smart_rollup_node/rollup_node_errors.ml @@ -66,6 +66,8 @@ type error += | Patch_durable_storage_on_commitment of int32 | Dal_message_too_big of {slot_size : int; message_size : int} +type error += PVM_eval_too_many_ticks of {max_given : int64; executed : int64} + type error += | Could_not_open_preimage_file of String.t | Could_not_encode_raw_data @@ -632,4 +634,30 @@ let () = Some (slot_size, message_size) | _ -> None) (fun (slot_size, message_size) -> - Dal_message_too_big {slot_size; message_size}) + Dal_message_too_big {slot_size; message_size}) ; + + register_error_kind + ~id:"sc_rollup.node.pvm_eval_too_many_ticks" + ~title:"PVM advanced too many ticks" + ~description: + "The rollup node expects the PVM to advance at most a given amount of \ + ticks. \n\ + NOTE: This is a potential security issue. Please email \ + security@tezos.com to have the problem investigated." + ~pp:(fun ppf (maximum, executed) -> + Format.fprintf + ppf + "The rollup node expected the PVM to advance at most %Ld ticks, but \ + the PVM executed %Ld ticks \n\ + NOTE: This is a potential security issue. Please email \ + security@tezos.com to have the problem investigated." + maximum + executed) + `Permanent + Data_encoding.( + obj2 (req "maximum ticks" int64) (req "executed ticks" int64)) + (function + | PVM_eval_too_many_ticks {max_given; executed} -> + Some (max_given, executed) + | _ -> None) + (fun (max_given, executed) -> PVM_eval_too_many_ticks {max_given; executed}) diff --git a/src/proto_alpha/lib_sc_rollup_node/fueled_pvm.ml b/src/proto_alpha/lib_sc_rollup_node/fueled_pvm.ml index bad555ed66ed..83a9d80d547b 100644 --- a/src/proto_alpha/lib_sc_rollup_node/fueled_pvm.ml +++ b/src/proto_alpha/lib_sc_rollup_node/fueled_pvm.ml @@ -175,11 +175,14 @@ module Make_fueled (F : Fuel.S) : FUELED_PVM with type fuel = F.t = struct Sc_rollup.Dal_parameters.encoding dal_parameters) in - let eval_tick fuel failing_ticks state = + let eval_tick_consume_fuel fuel failing_ticks state = let max_steps = F.max_ticks fuel in - let normal_eval ?(max_steps = max_steps) state = + let normal_eval ?(max_steps = max_steps) state fuel = Lwt.catch (fun () -> + (* This call to the PVM implementation of [eval_many] should never return running more + ticks than the given [max_steps] ticks. If it were to happen, this is a security issue, + and should be reported as such. *) let*! state, executed_ticks = PVM.eval_many ~reveal_builtins @@ -188,12 +191,27 @@ module Make_fueled (F : Fuel.S) : FUELED_PVM with type fuel = F.t = struct ~is_reveal_enabled state in - return (state, executed_ticks, failing_ticks)) + let* remaining_fuel = + match F.consume (F.of_ticks executed_ticks) fuel with + | None -> + tzfail + @@ Rollup_node_errors.PVM_eval_too_many_ticks + {max_given = max_steps; executed = executed_ticks} + | Some remaining_fuel -> Lwt_result.return remaining_fuel + in + return (state, executed_ticks, failing_ticks, remaining_fuel)) (function | Error_wrapper error -> Lwt.return (Error error) | exn -> Lwt.reraise exn) in - let failure_insertion_eval state tick failing_ticks' = + let normal_eval_wrapper ?(max_steps = max_steps) state fuel one_step_eval + = + let>* state, xticks, fticks, remaining_fuel = + normal_eval ~max_steps state fuel + in + return (state, xticks, fticks, remaining_fuel, one_step_eval) + in + let failure_insertion_eval state tick = let*! () = Interpreter_event.intended_failure ~level @@ -202,29 +220,21 @@ module Make_fueled (F : Fuel.S) : FUELED_PVM with type fuel = F.t = struct ~internal:true in let*! state = PVM.Internal_for_tests.insert_failure state in - return (state, 1L, failing_ticks') + return state in match failing_ticks with | xtick :: failing_ticks' -> + let one_step_eval state = failure_insertion_eval state xtick in let jump = Int64.(max 0L (pred xtick)) in if Compare.Int64.(jump = 0L) then (* Insert the failure in the first tick. *) - failure_insertion_eval state xtick failing_ticks' + return (state, xtick, failing_ticks', fuel, Some one_step_eval) else (* Jump just before the tick where we'll insert a failure. Nevertheless, we don't execute more than [max_steps]. *) let max_steps = Int64.max 0L max_steps |> Int64.min max_steps in - let open Delayed_write_monad.Lwt_result_syntax in - let>* state, executed_ticks, _failing_ticks = - normal_eval ~max_steps state - in - (* Insert the failure. *) - let>* state, executed_ticks', failing_ticks' = - failure_insertion_eval state xtick failing_ticks' - in - let executed_ticks = Int64.add executed_ticks executed_ticks' in - return (state, executed_ticks, failing_ticks') - | _ -> normal_eval state + normal_eval_wrapper ~max_steps state fuel (Some one_step_eval) + | _ -> normal_eval_wrapper state fuel None in let abort (state : PVM.tree) fuel current_tick = let state = PVM.Ctxt_wrapper.to_node_pvmstate state in @@ -239,18 +249,23 @@ module Make_fueled (F : Fuel.S) : FUELED_PVM with type fuel = F.t = struct match input_request with | No_input_required when F.is_empty fuel -> abort state fuel current_tick | No_input_required -> ( - let>* next_state, executed_ticks, failing_ticks = - eval_tick fuel failing_ticks state + let>* state, executed_ticks, failing_ticks, fuel, one_step_advancement + = + eval_tick_consume_fuel fuel failing_ticks state in - let fuel_executed = F.of_ticks executed_ticks in - match F.consume fuel_executed fuel with - | None -> abort state fuel current_tick - | Some fuel -> - go - fuel - (Int64.add current_tick executed_ticks) - failing_ticks - next_state) + let tick_after_eval_consume = Int64.add current_tick executed_ticks in + match one_step_advancement with + | None -> go fuel tick_after_eval_consume failing_ticks state + | Some one_step_eval -> ( + let>* state = one_step_eval state in + match F.consume F.one_tick_consumption fuel with + | None -> abort state fuel tick_after_eval_consume + | Some fuel -> + go + fuel + (Int64.succ tick_after_eval_consume) + failing_ticks + state)) | Needs_reveal (Reveal_raw_data hash) -> ( match F.consume F.one_tick_consumption fuel with | None -> abort state fuel current_tick @@ -344,7 +359,6 @@ module Make_fueled (F : Fuel.S) : FUELED_PVM with type fuel = F.t = struct | Aborted {state; fuel; _} -> return (Feed_input_aborted {state; fuel; fed_input = false}) | Completed {state; fuel; current_tick = tick; failing_ticks} -> ( - let open Delayed_write_monad.Lwt_result_syntax in match F.consume F.one_tick_consumption fuel with | None -> return (Feed_input_aborted {state; fuel; fed_input = false}) | Some fuel -> ( -- GitLab