diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/RPC_server.ml b/src/proto_016_PtMumbai/bin_sc_rollup_node/RPC_server.ml index d34d0ddebb97928f34056fa1cd1e6b1df5962875..71251190de5fda4402b57316e7a4d2a5dffe8f82 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/RPC_server.ml +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/RPC_server.ml @@ -30,7 +30,9 @@ open Protocol let get_head store = let open Lwt_result_syntax in let*! head = State.last_processed_head_opt store in - match head with None -> failwith "No head" | Some {hash; _} -> return hash + match head with + | None -> failwith "No head" + | Some {header = {block_hash; _}; _} -> return block_hash let get_finalized store = let open Lwt_result_syntax in @@ -51,18 +53,18 @@ let get_last_cemented (node_ctxt : _ Node_context.t) = let get_head_hash_opt store = let open Lwt_option_syntax in - let+ {hash; _} = State.last_processed_head_opt store in - hash + let+ {header = {block_hash; _}; _} = State.last_processed_head_opt store in + block_hash let get_head_level_opt store = let open Lwt_option_syntax in - let+ {level; _} = State.last_processed_head_opt store in - level + let+ {header = {level; _}; _} = State.last_processed_head_opt store in + Alpha_context.Raw_level.to_int32 level -let get_state_info_exn store block = +let get_l2_block_exn store block = let open Lwt_result_syntax in - let*! state = Store.StateInfo.get store block in - return state + let*! b = Store.L2_blocks.get store block in + return b module Slot_pages_map = struct open Protocol @@ -217,12 +219,22 @@ module Outbox_directory = Make_directory (struct end) module Common = struct + let () = + Block_directory.register0 Sc_rollup_services.Global.Block.block + @@ fun (node_ctxt, block) () () -> + let open Lwt_result_syntax in + let*! b = Node_context.get_full_l2_block node_ctxt block in + return b + let () = Block_directory.register0 Sc_rollup_services.Global.Block.num_messages @@ fun (node_ctxt, block) () () -> let open Lwt_result_syntax in - let+ state_info = get_state_info_exn node_ctxt.store block in - state_info.num_messages + let* l2_block = get_l2_block_exn node_ctxt.store block in + let*! {messages; _} = + Store.Messages.get node_ctxt.store l2_block.header.inbox_witness + in + return @@ Z.of_int (List.length messages) let () = Global_directory.register0 Sc_rollup_services.Global.sc_rollup_address @@ -252,8 +264,8 @@ module Common = struct Block_directory.register0 Sc_rollup_services.Global.Block.ticks @@ fun (node_ctxt, block) () () -> let open Lwt_result_syntax in - let+ state = get_state_info_exn node_ctxt.store block in - state.num_ticks + let+ l2_block = get_l2_block_exn node_ctxt.store block in + Z.of_int64 l2_block.num_ticks end module Make (Simulation : Simulation.S) (Batcher : Batcher.S) = struct @@ -359,14 +371,18 @@ module Make (Simulation : Simulation.S) (Batcher : Batcher.S) = struct Global_directory.register0 Sc_rollup_services.Global.last_stored_commitment @@ fun node_ctxt () () -> let open Lwt_result_syntax in - let*! commitment_with_hash = - Commitment.last_commitment_with_hash - (module Store.Last_stored_commitment_level) - node_ctxt.store + let*! res = + let open Lwt_option_syntax in + let* head = State.last_processed_head_opt node_ctxt.store in + let commitment_hash = + Sc_rollup_block.most_recent_commitment head.header + in + let*! commitment = + Store.Commitments.get node_ctxt.store commitment_hash + in + return (commitment, commitment_hash, None) in - return - (commitment_with_hash - |> Option.map (fun (commitment, hash) -> (commitment, hash, None))) + return res let () = Local_directory.register0 Sc_rollup_services.Local.last_published_commitment @@ -519,15 +535,25 @@ module Make (Simulation : Simulation.S) (Batcher : Batcher.S) = struct | None -> return (Sc_rollup_services.Included (info, inbox_info)) | Some commitment_level -> ( - let*! commitment = - Store.Commitments.find node_ctxt.store commitment_level + let*! block = + State.hash_of_level + node_ctxt.store + (Alpha_context.Raw_level.to_int32 commitment_level) in - match commitment with + let*! block = + Store.L2_blocks.find node_ctxt.store block + in + match block with | None -> (* Commitment not computed yet for inbox *) return (Sc_rollup_services.Included (info, inbox_info)) - | Some (commitment, commitment_hash) -> ( + | Some block -> ( + let commitment_hash = + WithExceptions.Option.get + ~loc:__LOC__ + block.header.commitment_hash + in (* Commitment computed *) let*! published_at = Store.Commitments_published_at_level.find @@ -541,6 +567,11 @@ module Make (Simulation : Simulation.S) (Batcher : Batcher.S) = struct (Sc_rollup_services.Included (info, inbox_info)) | Some published_at -> (* Commitment published *) + let*! commitment = + Store.Commitments.get + node_ctxt.store + commitment_hash + in let commitment_info = Sc_rollup_services. {commitment; commitment_hash; published_at} diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment.ml b/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment.ml index 40019e6dd72693dccf4d89732750bace15adcae1..fae9c06423e853f8e830932e8e3159ae1a80114e 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment.ml +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment.ml @@ -44,249 +44,178 @@ open Protocol open Alpha_context -module type Mutable_level_store = - Store_sigs.Mutable_value - with type value := Raw_level.t - and type 'a store := 'a Store.store - -(* We persist the number of ticks to be included in the - next commitment on disk, in a map that is indexed by - inbox level. Note that we do not risk to increase - these counters when the wrong branch is tracked by the rollup - node, as only finalized heads are processed to build commitments. -*) -module Number_of_ticks : - Store_sigs.Append_only_map - with type 'a store = 'a Store.store - and type key = Raw_level.t - and type value = Z.t = - Store.Make_append_only_map - (struct - let path = ["commitments"; "in_progress"; "number_of_ticks"] - end) - (struct - type key = Raw_level.t - - let to_path_representation key = Int32.to_string @@ Raw_level.to_int32 key - end) - (struct - type value = Z.t - - let name = "ticks" - - let encoding = Data_encoding.z - end) +let add_level level increment = + (* We only use this function with positive increments so it is safe *) + if increment < 0 then invalid_arg "Commitment.add_level negative increment" ; + Raw_level.Internal_for_tests.add level increment let sc_rollup_commitment_period node_ctxt = - Int32.of_int - @@ node_ctxt.Node_context.protocol_constants.parametric.sc_rollup - .commitment_period_in_blocks + node_ctxt.Node_context.protocol_constants.parametric.sc_rollup + .commitment_period_in_blocks let sc_rollup_challenge_window node_ctxt = - Int32.of_int - node_ctxt.Node_context.protocol_constants.parametric.sc_rollup - .challenge_window_in_blocks - -let last_commitment_level (module Last_commitment_level : Mutable_level_store) - store = - Last_commitment_level.find store - -let last_commitment_with_hash - (module Last_commitment_level : Mutable_level_store) store = - let open Lwt_option_syntax in - let* last_commitment_level = - last_commitment_level (module Last_commitment_level) store - in - let*! commitment_with_hash = - Store.Commitments.get store last_commitment_level - in - return commitment_with_hash + node_ctxt.Node_context.protocol_constants.parametric.sc_rollup + .challenge_window_in_blocks let next_lcc_level node_ctxt = - Environment.wrap_tzresult @@ Raw_level.of_int32 - @@ Int32.add - (Raw_level.to_int32 node_ctxt.Node_context.lcc.level) - (sc_rollup_commitment_period node_ctxt) - + add_level + node_ctxt.Node_context.lcc.level + (sc_rollup_commitment_period node_ctxt) + +(** Returns the next level for which a commitment can be published, i.e. the + level that is [commitment_period] blocks after the last published one. It + returns [None] if this level is not finalized because we only publish + commitments for inbox of finalized L1 blocks. *) let next_publishable_level node_ctxt = + let open Lwt_option_syntax in let lpc_level = match node_ctxt.Node_context.lpc with | None -> node_ctxt.genesis_info.level | Some lpc -> lpc.inbox_level in - Environment.wrap_tzresult @@ Raw_level.of_int32 - @@ Int32.add - (Raw_level.to_int32 lpc_level) - (sc_rollup_commitment_period node_ctxt) - -let next_commitment_level node_ctxt - (module Last_commitment_level : Mutable_level_store) = - let open Lwt_syntax in - let+ last_commitment_level_opt = - last_commitment_level - (module Last_commitment_level) - node_ctxt.Node_context.store - in - let last_commitment_level = - Option.value last_commitment_level_opt ~default:node_ctxt.genesis_info.level - in - Raw_level.of_int32 - @@ Int32.add - (Raw_level.to_int32 last_commitment_level) - (sc_rollup_commitment_period node_ctxt) - -let last_commitment_hash node_ctxt - (module Last_commitment_level : Mutable_level_store) = - let open Lwt_syntax in - let+ last_commitment = - last_commitment_with_hash - (module Last_commitment_level) - node_ctxt.Node_context.store - in - match last_commitment with - | Some (_commitment, hash) -> hash - | None -> node_ctxt.genesis_info.Sc_rollup.Commitment.commitment_hash - -let must_store_commitment node_ctxt current_level = - let open Lwt_result_syntax in - let+ next_commitment_level = - next_commitment_level node_ctxt (module Store.Last_stored_commitment_level) + let next_level = + add_level lpc_level (sc_rollup_commitment_period node_ctxt) in + let* finalized_level = State.get_finalized_head_opt node_ctxt.store in + if Raw_level.(of_int32_exn finalized_level.level < next_level) then fail + else return next_level - Raw_level.equal current_level next_commitment_level - -let update_last_stored_commitment (node_ctxt : _ Node_context.t) - (commitment : Sc_rollup.Commitment.t) = - let open Lwt_syntax in - let commitment_hash = Sc_rollup.Commitment.hash_uncarbonated commitment in - let inbox_level = commitment.inbox_level in - (* Do not change the order of these two operations. This guarantees that - whenever `Store.Last_stored_commitment_level.get` returns `Some hash`, - then the call to `Store.Commitments.get hash` will succeed. - *) - let* () = - Store.Commitments.add - node_ctxt.store - inbox_level - (commitment, commitment_hash) - in - let* () = - Store.Last_stored_commitment_level.set node_ctxt.store inbox_level - in - let* () = Commitment_event.commitment_stored commitment_hash commitment in - if commitment.inbox_level <= node_ctxt.lcc.level then - Commitment_event.commitment_will_not_be_published - node_ctxt.lcc.level - commitment - else return () +let next_commitment_level node_ctxt last_commitment_level = + add_level last_commitment_level (sc_rollup_commitment_period node_ctxt) module Make (PVM : Pvm.S) : Commitment_sig.S with module PVM = PVM = struct module PVM = PVM - let build_commitment node_ctxt block_hash = - let open Lwt_result_syntax in - let lsc = - (module Store.Last_stored_commitment_level : Mutable_level_store) - in - let*! predecessor = last_commitment_hash node_ctxt lsc in - let* inbox_level = - Lwt.map Environment.wrap_tzresult @@ next_commitment_level node_ctxt lsc + let tick_of_level (node_ctxt : _ Node_context.t) inbox_level = + let open Lwt_syntax in + let* block_hash = + State.hash_of_level node_ctxt.store (Raw_level.to_int32 inbox_level) in - let* ctxt = Node_context.checkout_context node_ctxt block_hash in + let+ block = Store.L2_blocks.get node_ctxt.store block_hash in + Sc_rollup_block.final_tick block + + let build_commitment (node_ctxt : _ Node_context.t) + (prev_commitment : Sc_rollup.Commitment.Hash.t) ~prev_commitment_level + ~inbox_level ctxt = + let open Lwt_result_syntax in let*! pvm_state = PVM.State.find ctxt in - let* compressed_state = + let*? pvm_state = match pvm_state with - | Some pvm_state -> - let*! hash = PVM.state_hash pvm_state in - return hash + | Some pvm_state -> Ok pvm_state | None -> - failwith - "PVM state for block hash not available %s" - (Tezos_crypto.Block_hash.to_string block_hash) + error_with + "PVM state for commitment at level %a is not available" + Raw_level.pp + inbox_level + in + let*! compressed_state = PVM.state_hash pvm_state in + let*! tick = PVM.get_tick pvm_state in + let*! prev_commitment_tick = + tick_of_level node_ctxt prev_commitment_level + in + let number_of_ticks = + Sc_rollup.Tick.distance tick prev_commitment_tick + |> Z.to_int64 |> Sc_rollup.Number_of_ticks.of_value in - let*! number_of_ticks = Number_of_ticks.get node_ctxt.store inbox_level in - let+ number_of_ticks = - match - Sc_rollup.Number_of_ticks.of_value @@ Z.to_int64 number_of_ticks - with + let*? number_of_ticks = + match number_of_ticks with | Some number_of_ticks -> if number_of_ticks = Sc_rollup.Number_of_ticks.zero then - failwith "A 0-tick commitment is impossible" - else return number_of_ticks - | None -> - failwith "Invalid number of ticks %s" (Z.to_string number_of_ticks) + error_with "A 0-tick commitment is impossible" + else Ok number_of_ticks + | None -> error_with "Invalid number of ticks for commitment" in - Sc_rollup.Commitment. - {predecessor; inbox_level; number_of_ticks; compressed_state} - - let store_commitment_if_necessary node_ctxt current_level block_hash = + return + Sc_rollup.Commitment. + { + predecessor = prev_commitment; + inbox_level; + number_of_ticks; + compressed_state; + } + + let create_commitment_if_necessary (node_ctxt : _ Node_context.t) ~predecessor + current_level ctxt = let open Lwt_result_syntax in - let* must_store_commitment = - Lwt.map Environment.wrap_tzresult - @@ must_store_commitment node_ctxt current_level - in - if must_store_commitment then - let*! () = Commitment_event.compute_commitment block_hash current_level in - let* commitment = build_commitment node_ctxt block_hash in - let*! () = update_last_stored_commitment node_ctxt commitment in - return_unit - else return_unit + if Raw_level.(current_level = node_ctxt.genesis_info.level) then + let+ genesis_commitment = + Plugin.RPC.Sc_rollup.commitment + node_ctxt.cctxt + (node_ctxt.cctxt#chain, `Head 0) + node_ctxt.rollup_address + node_ctxt.genesis_info.commitment_hash + in + Some genesis_commitment + else + let* last_commitment_hash = + let*! pred = Store.L2_blocks.find node_ctxt.store predecessor in + match pred with + | None -> + failwith "Missing block %a" Tezos_crypto.Block_hash.pp predecessor + | Some pred -> + return (Sc_rollup_block.most_recent_commitment pred.header) + in + let*! last_commitment = + Store.Commitments.get node_ctxt.store last_commitment_hash + in + let next_commitment_level = + next_commitment_level node_ctxt last_commitment.inbox_level + in + if Raw_level.(current_level = next_commitment_level) then + let*! () = Commitment_event.compute_commitment current_level in + let+ commitment = + build_commitment + node_ctxt + last_commitment_hash + ~prev_commitment_level:last_commitment.inbox_level + ~inbox_level:current_level + ctxt + in + Some commitment + else return_none - let update_ticks (node_ctxt : _ Node_context.t) current_level block_hash = + let process_head (node_ctxt : _ Node_context.t) ~predecessor Layer1.{level; _} + ctxt = let open Lwt_result_syntax in - let*! last_stored_commitment_level_opt = - last_commitment_level - (module Store.Last_stored_commitment_level) - node_ctxt.store - in - let last_stored_commitment_level = - Option.value - ~default:node_ctxt.genesis_info.level - last_stored_commitment_level_opt - in - let*! previous_level_num_ticks = - match Raw_level.pred current_level with - | None -> - (* This happens if the current_level is zero: it is safe to assume - that there are 0 ticks computed so far. *) - Lwt.return Z.zero - | Some level -> - if Raw_level.(level = last_stored_commitment_level) then - (* We are at the first level of a new commitment, so the initial amount - of ticks should be 0. *) - Lwt.return Z.zero - else if Raw_level.(level < node_ctxt.genesis_info.level) then - (* If the previous level was before the genesis level, then the - number of ticks at that level should be 0. *) - Lwt.return Z.zero - else - (* Otherwise we need to increment the number of ticks from the number - of ticks for the previous level. The number of ticks for such a - level should be in the store, otherwise the state of the rollup node - is corrupted. *) - Number_of_ticks.get node_ctxt.store level + let current_level = Raw_level.of_int32_exn level in + let* commitment = + create_commitment_if_necessary node_ctxt ~predecessor current_level ctxt in - let*! {num_ticks; _} = Store.StateInfo.get node_ctxt.store block_hash in - Number_of_ticks.add - node_ctxt.store - current_level - (Z.add previous_level_num_ticks num_ticks) + match commitment with + | None -> return_none + | Some commitment -> + let commitment_hash = + Sc_rollup.Commitment.hash_uncarbonated commitment + in + let*! () = + Store.Commitments.add node_ctxt.store commitment_hash commitment + in + return_some commitment_hash - let process_head (node_ctxt : _ Node_context.t) Layer1.{level; hash} = - let open Lwt_result_syntax in - let current_level = Raw_level.of_int32_exn level in - let*! () = update_ticks node_ctxt current_level hash in - store_commitment_if_necessary node_ctxt current_level hash + let block_of_known_level store level = + let open Lwt_option_syntax in + let* head = State.last_processed_head_opt store in + if Raw_level.(head.header.level < level) then + (* Level is not known yet *) + fail + else + let*! block_hash = State.hash_of_level store (Raw_level.to_int32 level) in + Store.L2_blocks.find store block_hash let get_commitment_and_publish ~check_lcc_hash ({store; _} as node_ctxt : _ Node_context.t) next_level_to_publish = let open Lwt_result_syntax in - let*! commitment = Store.Commitments.find store next_level_to_publish in + let*! commitment = + let open Lwt_option_syntax in + let* block = block_of_known_level store next_level_to_publish in + let*? commitment_hash = block.header.commitment_hash in + Store.Commitments.find node_ctxt.store commitment_hash + in match commitment with | None -> (* Commitment not available *) return_unit - | Some (commitment, _commitment_hash) -> ( + | Some commitment -> ( let* () = if check_lcc_hash then let open Lwt_result_syntax in @@ -332,11 +261,14 @@ module Make (PVM : Pvm.S) : Commitment_sig.S with module PVM = PVM = struct (* Check level of next publishable commitment and avoid publishing if it is on or before the last cemented commitment. *) - let*? next_lcc_level = next_lcc_level node_ctxt in - let*? next_publishable_level = next_publishable_level node_ctxt in - let check_lcc_hash, level_to_publish = - if Raw_level.(next_publishable_level < next_lcc_level) then - (* + let next_lcc_level = next_lcc_level node_ctxt in + let*! next_publishable_level = next_publishable_level node_ctxt in + match next_publishable_level with + | None -> return_unit + | Some next_publishable_level -> + let check_lcc_hash, level_to_publish = + if Raw_level.(next_publishable_level < next_lcc_level) then + (* This situation can happen if the rollup node has been shutdown and the rollup has been progressing in the @@ -350,10 +282,10 @@ module Make (PVM : Pvm.S) : Commitment_sig.S with module PVM = PVM = struct that's an invalid commitment. *) - (true, next_lcc_level) - else (false, next_publishable_level) - in - get_commitment_and_publish node_ctxt level_to_publish ~check_lcc_hash + (true, next_lcc_level) + else (false, next_publishable_level) + in + get_commitment_and_publish node_ctxt level_to_publish ~check_lcc_hash let earliest_cementing_level node_ctxt commitment_hash = let open Lwt_option_syntax in @@ -362,9 +294,7 @@ module Make (PVM : Pvm.S) : Commitment_sig.S with module PVM = PVM = struct node_ctxt.Node_context.store commitment_hash in - Int32.add - (Raw_level.to_int32 published_at_level) - (sc_rollup_challenge_window node_ctxt) + add_level published_at_level (sc_rollup_challenge_window node_ctxt) let can_be_cemented node_ctxt earliest_cementing_level head_level commitment_hash = @@ -393,16 +323,18 @@ module Make (PVM : Pvm.S) : Commitment_sig.S with module PVM = PVM = struct let* _hash = Injector.add_pending_operation ~source cement_operation in return_unit - let cement_commitment_if_possible node_ctxt Layer1.{level = head_level; _} = + let cement_commitment_if_possible ({Node_context.store; _} as node_ctxt) + Layer1.{level = head_level; _} = let open Lwt_result_syntax in - let*? next_level_to_cement = next_lcc_level node_ctxt in - let*! commitment_with_hash = - Store.Commitments.find node_ctxt.store next_level_to_cement - in - match commitment_with_hash with - (* If `commitment_with_hash` is defined, the commitment to be cemented has - been stored but not necessarily published by the rollup node. *) - | Some (_commitment, commitment_hash) -> ( + let next_level_to_cement = next_lcc_level node_ctxt in + let*! block = block_of_known_level store next_level_to_cement in + match block with + | None | Some {header = {commitment_hash = None; _}; _} -> + (* Commitment not available *) + return_unit + | Some {header = {commitment_hash = Some commitment_hash; _}; _} -> ( + (* If `commitment_hash` is defined, the commitment to be cemented has + been stored but not necessarily published by the rollup node. *) let*! earliest_cementing_level = earliest_cementing_level node_ctxt commitment_hash in @@ -415,13 +347,12 @@ module Make (PVM : Pvm.S) : Commitment_sig.S with module PVM = PVM = struct can_be_cemented node_ctxt earliest_cementing_level - head_level + (Raw_level.of_int32_exn head_level) commitment_hash in if green_flag then cement_commitment node_ctxt commitment_hash - else return () - | None -> return ()) - | None -> return () + else return_unit + | None -> return_unit) let start () = Commitment_event.starting () end diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment.mli b/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment.mli index 724914264ebb57972317d453b7880483c24d05e3..cb6c364d87cd6952c2306c2b3b520cc7089420e5 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment.mli +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment.mli @@ -39,26 +39,4 @@ commitment that was not published already. *) -open Protocol.Alpha_context - -module type Mutable_level_store = - Store_sigs.Mutable_value - with type value := Raw_level.t - and type 'a store := 'a Store.store - -(** [last_commitment_with_hash (module Last_level_module: Mutable_level_store) store] - returns the last commitment and relative hash - stored according to the value of level indicated by - [module Last_level_module]. If no commitment has been stored for the - level indicated by [module Last_level_module], then None is returned. - Two possible implementations for [module Last_level_module] are - [Store.Last_published_commitment_level] and - [Store.Last_stored_commitment_level]. - *) - -val last_commitment_with_hash : - (module Mutable_level_store) -> - _ Store.t -> - (Sc_rollup.Commitment.t * Sc_rollup.Commitment.Hash.t) option Lwt.t - module Make (PVM : Pvm.S) : Commitment_sig.S with module PVM = PVM diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment_event.ml b/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment_event.ml index 1f8e2c05e6a60c9de1fbbe43efd78350790c1e66..332a4e6013b68ab6ee44583efbd1cab13a5e7ad5 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment_event.ml +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment_event.ml @@ -75,13 +75,11 @@ module Simple = struct ("level", Raw_level.encoding) let compute_commitment = - declare_2 + declare_1 ~section ~name:"sc_rollup_node_commitment_process_head" - ~msg: - "Computing and storing new commitment for head {head} at level {level}" + ~msg:"Computing and storing new commitment for level {level}" ~level:Notice - ("head", Tezos_crypto.Block_hash.encoding) ("level", Raw_level.encoding) let commitment_parent_is_not_lcc = @@ -144,8 +142,7 @@ let commitment_stored = emit_commitment_event Simple.commitment_stored let last_cemented_commitment_updated head level = Simple.(emit last_cemented_commitment_updated (head, level)) -let compute_commitment head level = - Simple.(emit compute_commitment (head, level)) +let compute_commitment level = Simple.(emit compute_commitment level) let commitment_parent_is_not_lcc level predecessor_hash lcc_hash = Simple.(emit commitment_parent_is_not_lcc (level, predecessor_hash, lcc_hash)) diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment_event.mli b/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment_event.mli index bdca58229142ddfab1deaa3e643a7a1bf3d2d99b..600fc2d4b7d3bbd466a932abf71829a08f5f599e 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment_event.mli +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment_event.mli @@ -61,7 +61,6 @@ val commitment_parent_is_not_lcc : Sc_rollup.Commitment.Hash.t -> unit Lwt.t -(** [compute_commitment hash level] emits the event that a new commitment is - being computed and stored for the block of the given [hash] and at the given - [level]. *) -val compute_commitment : Tezos_crypto.Block_hash.t -> Raw_level.t -> unit Lwt.t +(** [compute_commitment level] emits the event that a new commitment is being + computed and stored for the block at the given [level]. *) +val compute_commitment : Raw_level.t -> unit Lwt.t diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment_sig.ml b/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment_sig.ml index eeacadea5cf06683a23a232373fae6cb0ce5bd2e..0b5faa4c6cf25ba7c986befaa630c912ec858fab 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment_sig.ml +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment_sig.ml @@ -42,12 +42,17 @@ module type S = sig module PVM : Pvm.S - (** [process_head node_ctxt head] checks whether a new commitment needs to be - computed and stored, by looking at the level of [head] and checking - whether it is a multiple of `Commitment.sc_rollup_commitment_period` - levels away from [node_ctxt.initial_level]. It uses the functionalities of - [PVM] to compute the hash of to be included in the commitment. *) - val process_head : Node_context.rw -> Layer1.head -> unit tzresult Lwt.t + (** [process_head node_ctxt ~predecessor head ctxt] builds a new commitment if + needed, by looking at the level of [head] and checking whether it is a + multiple of `Commitment.sc_rollup_commitment_period` levels away from + [node_ctxt.initial_level]. It uses the functionalities of [PVM] to compute + the hash of to be included in the commitment. *) + val process_head : + Node_context.rw -> + predecessor:Tezos_crypto.Block_hash.t -> + Layer1.head -> + Context.rw -> + Protocol.Alpha_context.Sc_rollup.Commitment.Hash.t option tzresult Lwt.t (** [publish_commitment node_ctxt] publishes the earliest commitment stored in [store] that has not been published yet, unless its inbox level diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/context.ml b/src/proto_016_PtMumbai/bin_sc_rollup_node/context.ml index 4c711cc857d1b1b9474038c656bced9d58f42759..0df91a965c46de08830287dea19dc6b3141a4f7d 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/context.ml +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/context.ml @@ -62,28 +62,21 @@ type ro = [`Read] t type commit = IStore.commit -type hash = IStore.hash +type hash = Sc_rollup_context_hash.t type path = string list -let hash_encoding = - let open Data_encoding in - conv - (fun h -> IStore.Hash.to_raw_string h |> Bytes.unsafe_of_string) - (fun b -> Bytes.unsafe_to_string b |> IStore.Hash.unsafe_of_raw_string) - (Fixed.bytes IStore.Hash.hash_size) +let () = assert (Sc_rollup_context_hash.size = IStore.Hash.hash_size) -let hash_to_raw_string = IStore.Hash.to_raw_string +let hash_to_istore_hash h = + Sc_rollup_context_hash.to_string h |> IStore.Hash.unsafe_of_raw_string -let pp_hash fmt h = - IStore.Hash.to_raw_string h - |> Hex.of_string |> Hex.show |> Format.pp_print_string fmt +let istore_hash_to_hash h = + IStore.Hash.to_raw_string h |> Sc_rollup_context_hash.of_string_exn let load : type a. a mode -> string -> a raw_index Lwt.t = - fun mode data_dir -> + fun mode path -> let open Lwt_syntax in - let open Configuration in - let path = default_context_dir data_dir in let readonly = match mode with Read_only -> true | Read_write -> false in let+ repo = IStore.Repo.v (Irmin_pack.config ~readonly path) in {path; repo} @@ -99,11 +92,11 @@ let raw_commit ?(message = "") index tree = let commit ?message ctxt = let open Lwt_syntax in let+ commit = raw_commit ?message ctxt.index ctxt.tree in - IStore.Commit.hash commit + IStore.Commit.hash commit |> istore_hash_to_hash let checkout index key = let open Lwt_syntax in - let* o = IStore.Commit.of_hash index.repo key in + let* o = IStore.Commit.of_hash index.repo (hash_to_istore_hash key) in match o with | None -> return_none | Some commit -> diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/context.mli b/src/proto_016_PtMumbai/bin_sc_rollup_node/context.mli index 2f5d4de887ae3b6e19241131c8b4831b163155ae..76a0f70c2924f459c71591a70118b672d621d814 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/context.mli +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/context.mli @@ -49,22 +49,12 @@ type ro = [`Read] t (** A context hash is the hash produced when the data of the context is committed to disk, i.e. the {!commit} hash. *) -type hash +type hash = Sc_rollup_context_hash.t (** The type of commits for the context. *) type commit -(** [hash_encoding] is the encoding for context hashes, of type {!hash}. *) -val hash_encoding : hash Data_encoding.t - -(** [hash_to_raw_string h] is the raw string representation for the hash [h]. *) -val hash_to_raw_string : hash -> string - -(** [pp_hash fmt h] prints the hash [h] in hexadecimal notation on the formatter - [fmt]. *) -val pp_hash : Format.formatter -> hash -> unit - -(** [load data_dir] initializes from disk a context using the [data_dir]. *) +(** [load path] initializes from disk a context from [path]. *) val load : 'a mode -> string -> 'a index Lwt.t (** [index context] is the repository of the context [context]. *) diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/daemon.ml b/src/proto_016_PtMumbai/bin_sc_rollup_node/daemon.ml index 9fba326ce95cf9efa90c18ce1a1b8e4267ae6a5b..f8370fe7bf5d9f54bc125e6add8a5b51cd765808 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/daemon.ml +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/daemon.ml @@ -190,7 +190,6 @@ module Make (PVM : Pvm.S) = struct in let*! () = Daemon_event.head_processing hash level ~finalized:true in let* () = process_l1_block_operations ~finalized:true node_ctxt block in - let* () = Components.Commitment.process_head node_ctxt block in let*! () = State.mark_finalized_head node_ctxt.store block in return_unit @@ -198,17 +197,60 @@ module Make (PVM : Pvm.S) = struct = let open Lwt_result_syntax in let*! () = Daemon_event.head_processing hash level ~finalized:false in - let* ctxt = Inbox.process_head node_ctxt head in + let*! () = State.set_block_level_and_hash node_ctxt.store head in + let* inbox_hash, inbox, inbox_witness, messages, ctxt = + Inbox.process_head node_ctxt head + in let* () = when_ (Node_context.dal_enabled node_ctxt) @@ fun () -> Dal_slots_tracker.process_head node_ctxt head in - let*! () = State.set_block_level_and_hash node_ctxt.store head in let* () = process_l1_block_operations ~finalized:false node_ctxt head in (* Avoid storing and publishing commitments if the head is not final. *) (* Avoid triggering the pvm execution if this has been done before for this head. *) - let* () = Components.Interpreter.process_head node_ctxt ctxt head in + let* ctxt, _num_messages, num_ticks, initial_tick = + Components.Interpreter.process_head node_ctxt ctxt head (inbox, messages) + in + let*! context_hash = Context.commit ctxt in + let* {Layer1.hash = predecessor; _} = + Layer1.get_predecessor node_ctxt.l1_ctxt head + in + let* commitment_hash = + Components.Commitment.process_head node_ctxt ~predecessor head ctxt + in + let level = Raw_level.of_int32_exn level in + let* previous_commitment_hash = + if level = node_ctxt.genesis_info.Sc_rollup.Commitment.level then + (* Previous commitment for rollup genesis is itself. *) + return node_ctxt.genesis_info.Sc_rollup.Commitment.commitment_hash + else + let*! pred = Store.L2_blocks.find node_ctxt.store predecessor in + match pred with + | None -> + failwith + "Missing L2 predecessor %a for previous commitment" + Tezos_crypto.Block_hash.pp + predecessor + | Some pred -> + return (Sc_rollup_block.most_recent_commitment pred.header) + in + let header = + Sc_rollup_block. + { + block_hash = hash; + level; + predecessor; + commitment_hash; + previous_commitment_hash; + context = context_hash; + inbox_witness; + inbox_hash; + } + in + let l2_block = + Sc_rollup_block.{header; content = (); num_ticks; initial_tick} + in let* finalized_block, _ = Layer1.nth_predecessor node_ctxt.l1_ctxt @@ -216,14 +258,16 @@ module Make (PVM : Pvm.S) = struct head in let* () = processed_finalized_block node_ctxt finalized_block in - let*! () = State.mark_processed_head node_ctxt.store head in + let*! () = State.save_l2_block node_ctxt.store l2_block in (* Publishing a commitment when one is available does not depend on the state of the current head. *) let* () = Components.Commitment.publish_commitment node_ctxt in let* () = Components.Commitment.cement_commitment_if_possible node_ctxt head in - let*! () = Daemon_event.new_head_processed hash level in + let*! () = + Daemon_event.new_head_processed hash (Raw_level.to_int32 level) + in return_unit let notify_injector {Node_context.l1_ctxt; _} new_head @@ -253,7 +297,13 @@ module Make (PVM : Pvm.S) = struct in let old_head = match old_head with - | Some old_head -> `Head old_head + | Some h -> + `Head + Layer1. + { + hash = h.header.block_hash; + level = Raw_level.to_int32 h.header.level; + } | None -> (* if no head has been processed yet, we want to handle all blocks since, and including, the rollup origination. *) @@ -469,7 +519,9 @@ let run ~data_dir (configuration : Configuration.t) let*! store = Store.load Read_write Configuration.(default_storage_dir data_dir) in - let*! context = Context.load Read_write data_dir in + let*! context = + Context.load Read_write (Configuration.default_context_dir data_dir) + in let* l1_ctxt, kind = Layer1.start configuration cctxt store in let* node_ctxt = Node_context.init diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/fueled_pvm.ml b/src/proto_016_PtMumbai/bin_sc_rollup_node/fueled_pvm.ml index d5b570a5e8d6c8e9fe7b7ff037a57f520c6acdbe..8b4b13c2c34d559862438a001e7604b7d65ab8b4 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/fueled_pvm.ml +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/fueled_pvm.ml @@ -36,16 +36,16 @@ module type S = sig type eval_result = {state : PVM.state; remaining_fuel : fuel; num_ticks : Z.t} - (** [eval_block_inbox ~fuel node_ctxt block_hash state] evaluates the - [messages] for the inbox of block [block_hash] in the given [state] of the - PVM and returns the evaluation results containing the new state, the - number of messages, the inbox level and the remaining fuel. *) + (** [eval_block_inbox ~fuel node_ctxt (inbox, messages) state] evaluates the + [messages] for the [inbox] in the given [state] of the PVM and returns the + evaluation results containing the new state, the number of messages, the + inbox level and the remaining fuel. *) val eval_block_inbox : fuel:fuel -> _ Node_context.t -> - Tezos_crypto.Block_hash.t -> + Sc_rollup.Inbox.t * Sc_rollup.Inbox_message.t list -> PVM.state -> - (PVM.state * Z.t * Raw_level.t * fuel) Node_context.delayed_write tzresult + (PVM.state * int * Raw_level.t * fuel) Node_context.delayed_write tzresult Lwt.t (** [eval_messages ?reveal_map ~fuel node_ctxt ~message_counter_offset state @@ -341,33 +341,26 @@ module Make (PVM : Pvm.S) = struct (state, fuel) messages - let eval_block_inbox ~fuel (Node_context.{store; _} as node_ctxt) hash - (state : PVM.state) : - (PVM.state * Z.t * Raw_level.t * fuel) Node_context.delayed_write + let eval_block_inbox ~fuel node_ctxt (inbox, messages) (state : PVM.state) : + (PVM.state * int * Raw_level.t * fuel) Node_context.delayed_write tzresult Lwt.t = - let open Lwt_result_syntax in let open Delayed_write_monad.Lwt_result_syntax in (* Obtain inbox and its messages for this block. *) - let*! inbox = Store.Inboxes.find store hash in - match inbox with - | None -> failwith "There is no empty inbox" - | Some inbox -> - let inbox_level = Inbox.inbox_level inbox in - let*! messages = Store.Messages.get store hash in - let num_messages = List.length messages |> Z.of_int in - (* Evaluate all the messages for this level. *) - let>* state, fuel = - eval_messages - ~reveal_map:None - ~fuel - node_ctxt - ~message_counter_offset:0 - state - inbox_level - messages - in - return (state, num_messages, inbox_level, fuel) + let inbox_level = Inbox.inbox_level inbox in + let num_messages = List.length messages in + (* Evaluate all the messages for this level. *) + let>* state, fuel = + eval_messages + ~reveal_map:None + ~fuel + node_ctxt + ~message_counter_offset:0 + state + inbox_level + messages + in + return (state, num_messages, inbox_level, fuel) let eval_messages ?reveal_map ~fuel node_ctxt ~message_counter_offset state inbox_level messages = diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/inbox.ml b/src/proto_016_PtMumbai/bin_sc_rollup_node/inbox.ml index 660a3fec27ef1f4e5da8f04261775dc80872c40f..fc5cf618fcde12d0326186f3a2b006a5b44ec939 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/inbox.ml +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/inbox.ml @@ -31,15 +31,19 @@ open Alpha_context let lift promise = Lwt.map Environment.wrap_tzresult promise +let genesis_inbox node_ctxt = + let genesis_level = + Raw_level.to_int32 node_ctxt.Node_context.genesis_info.level + in + Plugin.RPC.Sc_rollup.inbox + node_ctxt.cctxt + (node_ctxt.cctxt#chain, `Level genesis_level) + module State = struct let add_messages = Store.Messages.add let add_inbox = Store.Inboxes.add - let add_history = Store.Histories.add - - let add_messages_history = Store.Payloads_histories.add - let level_of_hash = State.level_of_hash (** [inbox_of_head node_ctxt store block] returns the latest inbox at the @@ -48,7 +52,11 @@ module State = struct let inbox_of_head node_ctxt Layer1.{hash = block_hash; level = block_level} = let open Lwt_result_syntax in let open Node_context in - let*! possible_inbox = Store.Inboxes.find node_ctxt.store block_hash in + let*! possible_inbox = + let open Lwt_option_syntax in + let* l2_block = Store.L2_blocks.find node_ctxt.store block_hash in + Store.Inboxes.find node_ctxt.store l2_block.header.inbox_hash + in (* Pre-condition: forall l. (l > genesis_level) => inbox[l] <> None. *) match possible_inbox with | None -> @@ -58,8 +66,8 @@ module State = struct at the end of the origination level. *) let genesis_level = Raw_level.to_int32 node_ctxt.genesis_info.level in if block_level = genesis_level then - let Node_context.{cctxt; _} = node_ctxt in - Plugin.RPC.Sc_rollup.inbox cctxt (cctxt#chain, `Level genesis_level) + let+ inbox = genesis_inbox node_ctxt in + inbox else if block_level > genesis_level then (* Invariant broken, the inbox for this level should exist. *) failwith @@ -75,27 +83,6 @@ module State = struct the scope of the rollup's node" block_level | Some inbox -> return inbox - - let history_of_head node_ctxt Layer1.{hash = block_hash; level = block_level} - = - let open Lwt_result_syntax in - let open Node_context in - let*! res = Store.Histories.find node_ctxt.store block_hash in - match res with - | Some history -> return history - | None -> - (* We won't find inboxes for blocks before the rollup origination level. - Fortunately this case will only ever be called once when dealing with - the rollup origination block. After that we would always find an - inbox. *) - let genesis_level = Raw_level.to_int32 node_ctxt.genesis_info.level in - if block_level <= genesis_level then - return @@ Sc_rollup.Inbox.History.empty ~capacity:60000L - else - failwith - "The inbox history for hash %a is missing." - Tezos_crypto.Block_hash.pp - block_hash end let get_messages Node_context.{l1_ctxt; _} head = @@ -162,28 +149,30 @@ let same_inbox_as_layer_1 node_ctxt head_hash inbox = (Sc_rollup.Inbox.equal layer1_inbox inbox) (Sc_rollup_node_errors.Inconsistent_inbox {layer1_inbox; inbox}) -let add_messages ~predecessor_timestamp ~predecessor inbox history messages = +let add_messages ~predecessor_timestamp ~predecessor inbox messages = let open Lwt_result_syntax in + let no_history = Sc_rollup.Inbox.History.empty ~capacity:0L in lift @@ let*? ( messages_history, - history, + _no_history, inbox, witness, messages_with_protocol_internal_messages ) = Sc_rollup.Inbox.add_all_messages ~predecessor_timestamp ~predecessor - history + no_history inbox messages in let witness_hash = Sc_rollup.Inbox_merkelized_payload_hashes.hash witness in + let inbox_hash = Sc_rollup.Inbox.hash inbox in return ( messages_history, witness_hash, - history, + inbox_hash, inbox, messages_with_protocol_internal_messages ) @@ -203,7 +192,6 @@ let process_head (node_ctxt : _ Node_context.t) *) let* predecessor = Layer1.get_predecessor node_ctxt.l1_ctxt head in let* inbox = State.inbox_of_head node_ctxt predecessor in - let* history = State.history_of_head node_ctxt predecessor in let*? level = Environment.wrap_tzresult @@ Raw_level.of_int32 level in let* ctxt = if Raw_level.(level <= node_ctxt.Node_context.genesis_info.level) then @@ -221,45 +209,73 @@ let process_head (node_ctxt : _ Node_context.t) (Raw_level.to_int32 level) (List.length collected_messages) in - let* ( messages_history, - messages_hash, - history, + let* ( _messages_history, + witness_hash, + inbox_hash, inbox, messages_with_protocol_internal_messages ) = add_messages ~predecessor_timestamp ~predecessor:predecessor_hash inbox - history collected_messages in let*! () = State.add_messages node_ctxt.store - head_hash - messages_with_protocol_internal_messages + witness_hash + { + predecessor = predecessor_hash; + predecessor_timestamp; + messages = collected_messages; + } in let* () = same_inbox_as_layer_1 node_ctxt head_hash inbox in - let*! () = - State.add_messages_history node_ctxt.store messages_hash messages_history - in - let*! () = State.add_inbox node_ctxt.store head_hash inbox in - let*! () = State.add_history node_ctxt.store head_hash history in - return ctxt - else return (Context.empty node_ctxt.context) + let*! () = State.add_inbox node_ctxt.store inbox_hash inbox in + return + ( inbox_hash, + inbox, + witness_hash, + messages_with_protocol_internal_messages, + ctxt ) + else + let* inbox = genesis_inbox node_ctxt in + return + ( Sc_rollup.Inbox.hash inbox, + inbox, + Sc_rollup.Inbox.current_witness inbox, + [], + Context.empty node_ctxt.context ) let inbox_of_hash node_ctxt hash = let open Lwt_result_syntax in let* level = State.level_of_hash node_ctxt.Node_context.store hash in State.inbox_of_head node_ctxt {hash; level} -let history_of_hash node_ctxt hash = - let open Lwt_result_syntax in - let* level = State.level_of_hash node_ctxt.Node_context.store hash in - State.history_of_head node_ctxt {hash; level} - let inbox_of_head = State.inbox_of_head -let history_of_head = State.history_of_head - let start () = Inbox_event.starting () + +let payloads_history_of_messages ~predecessor ~predecessor_timestamp messages = + let open Result_syntax in + Environment.wrap_tzresult + @@ let* dummy_inbox = + (* The inbox is not necessary to compute the payloads *) + Sc_rollup.Inbox.genesis + ~predecessor_timestamp + ~predecessor + Raw_level.root + in + let+ ( payloads_history, + _history, + _inbox, + _witness, + _messages_with_protocol_internal_messages ) = + Sc_rollup.Inbox.add_all_messages + ~predecessor_timestamp + ~predecessor + (Sc_rollup.Inbox.History.empty ~capacity:0L) + dummy_inbox + messages + in + payloads_history diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/inbox.mli b/src/proto_016_PtMumbai/bin_sc_rollup_node/inbox.mli index 6f3dbbebc5e68abcdcf31a5229b7f05539144439..4765aceb02d15b53ccc68c08dea340b3caff0eed 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/inbox.mli +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/inbox.mli @@ -39,44 +39,52 @@ open Sc_rollup (** [process_head node_ctxt head operations] changes the state of the inbox to react to [head]. In particular, this process filters the provided [operations] of the [head] block. *) -val process_head : Node_context.rw -> Layer1.head -> Context.rw tzresult Lwt.t +val process_head : + Node_context.rw -> + Layer1.head -> + (Sc_rollup.Inbox.Hash.t + * Sc_rollup.Inbox.t + * Sc_rollup.Inbox_merkelized_payload_hashes.Hash.t + * Sc_rollup.Inbox_message.t list + * Context.rw) + tzresult + Lwt.t (** [inbox_of_hash node_ctxt block_hash] returns the rollup inbox at the end of - the given validation of [block_hash]. *) + the given validation of [block_hash]. NOTE: It requires the L2 block for + [block_hash] to have been saved. *) val inbox_of_hash : _ Node_context.t -> Tezos_crypto.Block_hash.t -> Inbox.t tzresult Lwt.t -(** [history_of_hash node_ctxt block_hash] returns the rollup inbox history at - the end of the given validation of [block_hash]. *) -val history_of_hash : - _ Node_context.t -> - Tezos_crypto.Block_hash.t -> - Inbox.History.t tzresult Lwt.t - (** [inbox_of_head node_ctxt block_head] returns the rollup inbox at the end of - the given validation of [block_head]. *) + the given validation of [block_head]. NOTE: It requires the L2 block for + [block_hash] to have been saved. *) val inbox_of_head : _ Node_context.t -> Layer1.head -> Inbox.t tzresult Lwt.t -(** [history_of_head node_ctxt block_head] returns the rollup inbox history at - the end of the given validation of [block_head]. *) -val history_of_head : - _ Node_context.t -> Layer1.head -> Inbox.History.t tzresult Lwt.t - (** [start ()] initializes the inbox to track the messages being published. *) val start : unit -> unit Lwt.t -(** [add_messages ~timestamp ~predecessor inbox history messages] adds - [messages] to the [inbox] using {Inbox.add_all_messages}. *) +(** [add_messages ~predecessor_timestamp ~predecessor inbox messages] adds + [messages] to the [inbox] using {!Inbox.add_all_messages}. *) val add_messages : predecessor_timestamp:Timestamp.time -> predecessor:Tezos_crypto.Block_hash.t -> Inbox.t -> - Inbox.History.t -> Inbox_message.t list -> (Inbox_merkelized_payload_hashes.History.t * Inbox_merkelized_payload_hashes.Hash.t - * Inbox.History.t + * Inbox.Hash.t * Inbox.t * Inbox_message.t list) tzresult Lwt.t + +(** [payloads_history_of_messages ~predecessor ~predecessor_timestamp messages] + builds the payloads history for the list of [messages]. This allows to not + store payloads histories (which contain merkelized skip lists) but simply + messages. *) +val payloads_history_of_messages : + predecessor:Tezos_crypto.Block_hash.t -> + predecessor_timestamp:Timestamp.time -> + Sc_rollup.Inbox_message.t list -> + Sc_rollup.Inbox_merkelized_payload_hashes.History.t tzresult diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/interpreter.ml b/src/proto_016_PtMumbai/bin_sc_rollup_node/interpreter.ml index 711bf576cdddc3f7043e20a5235840b5817d2668..88918759e11afbe80d5e73714ed432c6ffb4389b 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/interpreter.ml +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/interpreter.ml @@ -36,7 +36,11 @@ module type S = sig Fueled_pvm.S with module PVM = PVM and type fuel = Fuel.Free.t val process_head : - Node_context.rw -> Context.rw -> Layer1.head -> unit tzresult Lwt.t + Node_context.rw -> + 'a Context.t -> + Layer1.head -> + Sc_rollup.Inbox.t * Sc_rollup.Inbox_message.t list -> + ('a Context.t * int * int64 * Sc_rollup.Tick.t) tzresult Lwt.t val state_of_tick : _ Node_context.t -> @@ -126,7 +130,8 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct (** [transition_pvm node_ctxt predecessor head] runs a PVM at the previous state from block [predecessor] by consuming as many messages as possible from block [head]. *) - let transition_pvm node_ctxt ctxt predecessor Layer1.{hash; _} = + let transition_pvm node_ctxt ctxt predecessor Layer1.{hash = _; _} + inbox_messages = let open Lwt_result_syntax in (* Retrieve the previous PVM state from store. *) let* ctxt, predecessor_state = state_of_head node_ctxt ctxt predecessor in @@ -134,46 +139,22 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct Free_pvm.eval_block_inbox ~fuel:(Fuel.Free.of_ticks 0L) node_ctxt - hash + inbox_messages predecessor_state in let*! state, num_messages, inbox_level, _fuel = Delayed_write_monad.apply node_ctxt eval_result in - (* Write final state to store. *) let*! ctxt = PVM.State.set ctxt state in - let*! context_hash = Context.commit ctxt in - let*! () = Store.Contexts.add node_ctxt.store hash context_hash in - - (* Compute extra information about the state. *) let*! initial_tick = PVM.get_tick predecessor_state in - - let*! () = - let open Store.StateHistoryRepr in - let Layer1.{hash = predecessor_hash; _} = predecessor in - let event = - { - tick = initial_tick; - block_hash = hash; - predecessor_hash; - level = inbox_level; - } - in - Store.StateHistory.insert node_ctxt.store event - in - let*! last_tick = PVM.get_tick state in (* TODO: #2717 The number of ticks should not be an arbitrarily-sized integer or the difference between two ticks should be made an arbitrarily-sized integer too. *) - let num_ticks = Sc_rollup.Tick.distance initial_tick last_tick in - let*! () = - Store.StateInfo.add - node_ctxt.store - hash - {num_messages; num_ticks; initial_tick} + let num_ticks = + Sc_rollup.Tick.distance initial_tick last_tick |> Z.to_int64 in (* Produce events. *) let*! state_hash = PVM.state_hash state in @@ -184,11 +165,10 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct last_tick num_messages in - - return_unit + return (ctxt, num_messages, num_ticks, initial_tick) (** [process_head node_ctxt head] runs the PVM for the given head. *) - let process_head (node_ctxt : _ Node_context.t) ctxt head = + let process_head (node_ctxt : _ Node_context.t) ctxt head inbox_messages = let open Lwt_result_syntax in let first_inbox_level = Raw_level.to_int32 node_ctxt.genesis_info.level |> Int32.succ @@ -197,46 +177,48 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct let* predecessor = Layer1.get_predecessor node_ctxt.Node_context.l1_ctxt head in - transition_pvm node_ctxt ctxt predecessor head + transition_pvm node_ctxt ctxt predecessor head inbox_messages else if head.Layer1.level = Raw_level.to_int32 node_ctxt.genesis_info.level then let* ctxt, state = genesis_state head.hash node_ctxt ctxt in (* Write final state to store. *) let*! ctxt = PVM.State.set ctxt state in - let*! context_hash = Context.commit ctxt in - let*! () = Store.Contexts.add node_ctxt.store head.hash context_hash in - let*! () = - Store.StateInfo.add - node_ctxt.store - head.hash - { - num_messages = Z.zero; - num_ticks = Z.zero; - initial_tick = Sc_rollup.Tick.initial; - } - in - return_unit - else return_unit + (* let*! context_hash = Context.commit ctxt in *) + (* let*! () = Store.Contexts.add node_ctxt.store head.hash context_hash in *) + return (ctxt, 0, 0L, Sc_rollup.Tick.initial) + else return (ctxt, 0, 0L, Sc_rollup.Tick.initial) - (** [run_for_ticks node_ctxt predecessor_hash hash tick_distance] starts the - evaluation of the inbox at block [hash] for at most [tick_distance]. *) - let run_for_ticks node_ctxt predecessor_hash hash level tick_distance = + (** [run_for_ticks node_ctxt block tick_distance] starts the + evaluation of the inbox at [block] for at most [tick_distance]. *) + let run_for_ticks node_ctxt (block : Sc_rollup_block.t) tick_distance = let open Lwt_result_syntax in let open Delayed_write_monad.Lwt_result_syntax in - let pred_level = Raw_level.to_int32 level |> Int32.pred in - let* ctxt = Node_context.checkout_context node_ctxt predecessor_hash in + let pred_level = Raw_level.to_int32 block.header.level |> Int32.pred in + let* ctxt = + Node_context.checkout_context node_ctxt block.header.predecessor + in let* _ctxt, state = state_of_head node_ctxt ctxt - Layer1.{hash = predecessor_hash; level = pred_level} + Layer1.{hash = block.header.predecessor; level = pred_level} + in + let*! inbox = Store.Inboxes.get node_ctxt.store block.header.inbox_hash in + let*! {predecessor; predecessor_timestamp; messages} = + Store.Messages.get node_ctxt.store block.header.inbox_witness + in + let messages = + Sc_rollup.Inbox_message.Internal Start_of_level + :: Internal (Info_per_level {predecessor; predecessor_timestamp}) + :: messages + @ [Internal End_of_level] in let>* state, _counter, _level, _fuel = Accounted_pvm.eval_block_inbox ~fuel:(Fuel.Accounted.of_ticks tick_distance) node_ctxt - hash + (inbox, messages) state in return state @@ -246,18 +228,14 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct [None].*) let state_of_tick node_ctxt tick level = let open Lwt_result_syntax in - let* closest_event = - Store.StateHistory.event_of_largest_tick_before - node_ctxt.Node_context.store - tick - in - match closest_event with + let* closest_block = State.block_before node_ctxt.Node_context.store tick in + match closest_block with | None -> return None | Some event -> - if Raw_level.(event.level > level) then return None + if Raw_level.(event.header.level > level) then return None else let tick_distance = - Sc_rollup.Tick.distance tick event.tick |> Z.to_int64 + Sc_rollup.Tick.distance tick event.initial_tick |> Z.to_int64 in (* TODO: #3384 We assume that [StateHistory] correctly stores enough @@ -266,14 +244,7 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct [event.block_hash] is the block where the tick happened. We should test that this is always true because [state_of_tick] is a critical function. *) - let* state = - run_for_ticks - node_ctxt - event.predecessor_hash - event.block_hash - event.level - tick_distance - in + let* state = run_for_ticks node_ctxt event tick_distance in let state = Delayed_write_monad.ignore state in let*! hash = PVM.state_hash state in return (Some (state, hash)) diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/interpreter.mli b/src/proto_016_PtMumbai/bin_sc_rollup_node/interpreter.mli index 9f3fbc27871ee964a84fb4f3e215294c62d7d988..18281abb76f28d17fec42853273ca1f28d9aaa93 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/interpreter.mli +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/interpreter.mli @@ -34,11 +34,19 @@ module type S = sig module Free_pvm : Fueled_pvm.S with module PVM = PVM and type fuel = Fuel.Free.t - (** [process_head node_ctxt head] interprets the messages associated - with a [head] from a chain [event]. This requires the inbox to be updated - beforehand. *) + (** [process_head node_ctxt head (inbox, messages)] interprets the [messages] + associated with a [head]. This requires the [inbox] to be updated + beforehand. It returns [(ctxt, num_messages, num_ticks, tick)] where + [ctxt] is the updated layer 2 context (with the new PVM state), + [num_messages] is the number of [messages], [num_ticks] is the number of + ticks taken by the PVM for the evaluation and [tick] is the tick reached + by the PVM after the evaluation. *) val process_head : - Node_context.rw -> Context.rw -> Layer1.head -> unit tzresult Lwt.t + Node_context.rw -> + 'a Context.t -> + Layer1.head -> + Sc_rollup.Inbox.t * Sc_rollup.Inbox_message.t list -> + ('a Context.t * int * int64 * Sc_rollup.Tick.t) tzresult Lwt.t (** [state_of_tick node_ctxt tick level] returns [Some (state, hash)] for a given [tick] if this [tick] happened before diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/interpreter_event.ml b/src/proto_016_PtMumbai/bin_sc_rollup_node/interpreter_event.ml index 49152eed397aa73e17c7549827d6696fa223717d..ffc016b9123b695c0084c840b202617a92dede29 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/interpreter_event.ml +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/interpreter_event.ml @@ -41,7 +41,7 @@ module Simple = struct ("inbox_level", Protocol.Alpha_context.Raw_level.encoding) ("state_hash", State_hash.encoding) ("ticks", Tick.encoding) - ("num_messages", Data_encoding.z) + ("num_messages", Data_encoding.int31) let intended_failure = declare_4 diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/node_context.ml b/src/proto_016_PtMumbai/bin_sc_rollup_node/node_context.ml index f4d20c3970da48ba47043273bd194c824cfbed0b..d05a41ceac5549802df864f44a4e67940cc2fd1a 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/node_context.ml +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/node_context.ml @@ -143,19 +143,19 @@ let init (cctxt : Protocol_client_context.full) dal_cctxt ~data_dir l1_ctxt let checkout_context node_ctxt block_hash = let open Lwt_result_syntax in - let*! context_hash = Store.Contexts.find node_ctxt.store block_hash in + let*! l2_block = Store.L2_blocks.find node_ctxt.store block_hash in let*? context_hash = - match context_hash with + match l2_block with | None -> error (Sc_rollup_node_errors.Cannot_checkout_context (block_hash, None)) - | Some context_hash -> ok context_hash + | Some {header = {context; _}; _} -> ok context in let*! ctxt = Context.checkout node_ctxt.context context_hash in match ctxt with | None -> tzfail (Sc_rollup_node_errors.Cannot_checkout_context - (block_hash, Some (Context.hash_to_raw_string context_hash))) + (block_hash, Some context_hash)) | Some ctxt -> return ctxt let metadata node_ctxt = @@ -174,3 +174,13 @@ let readonly (node_ctxt : _ t) = } type 'a delayed_write = ('a, rw) Delayed_write_monad.t + +let get_full_l2_block {store; _} block_hash = + let open Lwt_syntax in + let* block = Store.L2_blocks.get store block_hash in + let* inbox = Store.Inboxes.get store block.header.inbox_hash + and* {messages; _} = Store.Messages.get store block.header.inbox_witness + and* commitment = + Option.map_s (Store.Commitments.get store) block.header.commitment_hash + in + return {block with content = {Sc_rollup_block.inbox; messages; commitment}} diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/node_context.mli b/src/proto_016_PtMumbai/bin_sc_rollup_node/node_context.mli index db431e7a77fd6b23f25b851a8f3d97fdf7686cef..3611b0e41cca26bacd35af34e8be9fa6c7758a65 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/node_context.mli +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/node_context.mli @@ -127,3 +127,9 @@ val readonly : _ t -> ro (** Monad for values with delayed write effects in the node context. *) type 'a delayed_write = ('a, rw) Delayed_write_monad.t + +(** [get_full_l2_block node_ctxt hash] returns the full L2 block for L1 block + hash [hash]. The result contains the L2 block and its content (inbox, + messages, commitment). *) +val get_full_l2_block : + _ t -> Tezos_crypto.Block_hash.t -> Sc_rollup_block.full Lwt.t diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/refutation_game.ml b/src/proto_016_PtMumbai/bin_sc_rollup_node/refutation_game.ml index 39811449f42282b59c4a21743683617e9cc7adcf..b215f13c6ec8f46cbf705301713faf405d26db70 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/refutation_game.ml +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/refutation_game.ml @@ -171,8 +171,7 @@ module Make (Interpreter : Interpreter.S) : let snapshot_head = Layer1.{hash = snapshot_hash; level = snapshot_level_int32} in - let* snapshot_inbox = Inbox.inbox_of_hash node_ctxt snapshot_hash in - let* snapshot_history = Inbox.history_of_hash node_ctxt snapshot_hash in + let* snapshot_inbox = Inbox.inbox_of_head node_ctxt snapshot_head in let* snapshot_ctxt = Node_context.checkout_context node_ctxt snapshot_hash in @@ -225,10 +224,20 @@ module Make (Interpreter : Interpreter.S) : let inbox = snapshot let get_history inbox_hash = - Sc_rollup.Inbox.History.find inbox_hash snapshot_history |> Lwt.return - - let get_payloads_history = - Store.Payloads_histories.get node_ctxt.Node_context.store + let open Lwt_option_syntax in + let+ inbox = Store.Inboxes.find node_ctxt.store inbox_hash in + Sc_rollup.Inbox.take_snapshot inbox + + let get_payloads_history witness = + let open Lwt_syntax in + let+ {predecessor; predecessor_timestamp; messages} = + Store.Messages.get node_ctxt.store witness + in + Inbox.payloads_history_of_messages + ~predecessor + ~predecessor_timestamp + messages + |> WithExceptions.Result.get_ok ~loc:__LOC__ end module Dal_with_history = struct @@ -249,7 +258,7 @@ module Make (Interpreter : Interpreter.S) : let* proof = trace (Sc_rollup_node_errors.Cannot_produce_proof - (snapshot_inbox, snapshot_history, game.inbox_level)) + (snapshot_inbox, game.inbox_level)) @@ (Sc_rollup.Proof.produce ~metadata (module P) game.inbox_level >|= Environment.wrap_tzresult) in diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/sc_rollup_node_errors.ml b/src/proto_016_PtMumbai/bin_sc_rollup_node/sc_rollup_node_errors.ml index 1328c3c30f94afa155ce4cc7a77b18784898f883..ceabd95165019890c2df93aa02a6a9762dc55f07 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/sc_rollup_node_errors.ml +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/sc_rollup_node_errors.ml @@ -28,8 +28,7 @@ open Protocol.Alpha_context let tez_sym = "\xEA\x9C\xA9" type error += - | Cannot_produce_proof of - Sc_rollup.Inbox.t * Sc_rollup.Inbox.History.t * Raw_level.t + | Cannot_produce_proof of Sc_rollup.Inbox.t * Raw_level.t | Missing_mode_operators of {mode : string; missing_operators : string list} | Bad_minimal_fees of string | Commitment_predecessor_should_be_LCC of Sc_rollup.Commitment.t @@ -39,7 +38,8 @@ type error += inbox : Sc_rollup.Inbox.t; } | Missing_PVM_state of Tezos_crypto.Block_hash.t * Int32.t - | Cannot_checkout_context of Tezos_crypto.Block_hash.t * string option + | Cannot_checkout_context of + Tezos_crypto.Block_hash.t * Sc_rollup_context_hash.t option | No_batcher type error += @@ -99,27 +99,21 @@ let () = ~description: "The rollup node is in a state that prevents it from producing \ refutation proofs." - ~pp:(fun ppf (inbox, history, level) -> + ~pp:(fun ppf (inbox, level) -> Format.fprintf ppf - "cannot produce proof for inbox %a of level %a with history %a" + "cannot produce proof for inbox %a of level %a" Sc_rollup.Inbox.pp inbox Raw_level.pp - level - Sc_rollup.Inbox.History.pp - history) + level) Data_encoding.( - obj3 + obj2 (req "inbox" Sc_rollup.Inbox.encoding) - (req "history" Sc_rollup.Inbox.History.encoding) (req "level" Raw_level.encoding)) (function - | Cannot_produce_proof (inbox, history, level) -> - Some (inbox, history, level) - | _ -> None) - (fun (inbox, history, level) -> - Cannot_produce_proof (inbox, history, level)) ; + | Cannot_produce_proof (inbox, level) -> Some (inbox, level) | _ -> None) + (fun (inbox, level) -> Cannot_produce_proof (inbox, level)) ; register_error_kind ~id:"sc_rollup.node.missing_mode_operators" @@ -197,14 +191,14 @@ let () = "The context %sfor block %a cannot be checkouted" (Option.fold ~none:"" - ~some:(fun c -> Hex.(show (of_string c))) + ~some:Sc_rollup_context_hash.to_b58check context_hash) Tezos_crypto.Block_hash.pp block) Data_encoding.( obj2 (req "block" Tezos_crypto.Block_hash.encoding) - (opt "context" (conv Bytes.of_string Bytes.to_string bytes))) + (opt "context" Sc_rollup_context_hash.encoding)) (function | Cannot_checkout_context (block, context) -> Some (block, context) | _ -> None) diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/state.ml b/src/proto_016_PtMumbai/bin_sc_rollup_node/state.ml index ff76af338d9c0e7151d8b8887c193f588bfb98d6..7d916f43ec266c6a4e19f8b2133022530bf657bd 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/state.ml +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/state.ml @@ -22,41 +22,28 @@ (* DEALINGS IN THE SOFTWARE. *) (* *) (*****************************************************************************) +open Protocol +open Alpha_context +module Raw_store = Store module Store = struct - (** Table from blocks hashes to unit. The entry is present iff the block - identified by that hash is fully processed by the rollup node. *) - module Processed_hashes = - Store.Make_append_only_map - (struct - let path = ["tezos"; "processed_blocks"] - end) - (struct - type key = Tezos_crypto.Block_hash.t - - let to_path_representation = Tezos_crypto.Block_hash.to_b58check - end) - (struct - type value = unit - - let name = "processed" - - let encoding = Data_encoding.unit - end) - - module Last_processed_head = + (* TODO: https://gitlab.com/tezos/tezos/-/issues/4392 + Use file. *) + module L2_head = Store.Make_mutable_value (struct - let path = ["tezos"; "processed_head"] + let path = ["l2_head"] end) (struct - type value = Layer1.head + type value = Sc_rollup_block.t - let name = "head" + let name = "l2_block" - let encoding = Layer1.head_encoding + let encoding = Sc_rollup_block.encoding end) + (* TODO: https://gitlab.com/tezos/tezos/-/issues/4392 + Use file. *) module Last_finalized_head = Store.Make_mutable_value (struct @@ -119,14 +106,14 @@ let level_of_hash store hash = failwith "No level known for block %a" Tezos_crypto.Block_hash.pp hash | Some l -> return l -let mark_processed_head store Layer1.({hash; level = _} as head) = +let save_l2_block store (head : Sc_rollup_block.t) = let open Lwt_syntax in - let* () = Store.Processed_hashes.add store hash () in - Store.Last_processed_head.set store head + let* () = Raw_store.L2_blocks.add store head.header.block_hash head in + Store.L2_head.set store head -let is_processed store head = Store.Processed_hashes.mem store head +let is_processed store head = Raw_store.L2_blocks.mem store head -let last_processed_head_opt store = Store.Last_processed_head.find store +let last_processed_head_opt store = Store.L2_head.find store let mark_finalized_head store head = Store.Last_finalized_head.set store head @@ -136,3 +123,24 @@ let set_block_level_and_hash store Layer1.{hash; level} = let open Lwt_syntax in let* () = Store.Hashes_to_levels.add store hash level in Store.Levels_to_hashes.add store level hash + +(* TODO: https://gitlab.com/tezos/tezos/-/issues/4532 + Make this logarithmic, by storing pointers to muliple predecessor and + by dichotomy. *) +let block_before store tick = + let open Lwt_result_syntax in + let*! head = Store.L2_head.find store in + match head with + | None -> return_none + | Some head -> + let rec search block_hash = + let*! block = Raw_store.L2_blocks.find store block_hash in + match block with + | None -> + failwith "Missing block %a" Tezos_crypto.Block_hash.pp block_hash + | Some block -> + if Sc_rollup.Tick.(block.initial_tick <= tick) then + return_some block + else search block.header.predecessor + in + search head.header.block_hash diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/state.mli b/src/proto_016_PtMumbai/bin_sc_rollup_node/state.mli index 0b6e426bf6bd9f593859b00eebfdccc245fe153b..9db757186c6d894cdb91d642ef20637daef2cbea 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/state.mli +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/state.mli @@ -23,17 +23,19 @@ (* *) (*****************************************************************************) +open Protocol.Alpha_context + (** [is_processed store hash] returns [true] if the block with [hash] has already been processed by the daemon. *) val is_processed : _ Store.t -> Tezos_crypto.Block_hash.t -> bool Lwt.t (** [mark_processed_head store head] remembers that the [head] is processed. The system should not have to come back to it. *) -val mark_processed_head : Store.rw -> Layer1.head -> unit Lwt.t +val save_l2_block : Store.rw -> Sc_rollup_block.t -> unit Lwt.t (** [last_processed_head_opt store] returns the last processed head if it exists. *) -val last_processed_head_opt : _ Store.t -> Layer1.head option Lwt.t +val last_processed_head_opt : _ Store.t -> Sc_rollup_block.t option Lwt.t (** [mark_finalized_head store head] remembers that the [head] is finalized. By construction, every block whose level is smaller than [head]'s is also @@ -56,3 +58,10 @@ val level_of_hash : (** [set_block_level_and_hash store head] registers the correspondences [head.level |-> head.hash] and [head.hash |-> head.level] in the store. *) val set_block_level_and_hash : Store.rw -> Layer1.head -> unit Lwt.t + +(** [block_before store tick] returns the last layer 2 block whose initial tick + is before [tick]. *) +val block_before : + [> `Read] Store.store -> + Sc_rollup.Tick.t -> + Sc_rollup_block.t option tzresult Lwt.t diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/store.ml b/src/proto_016_PtMumbai/bin_sc_rollup_node/store.ml index 4fd308e17a66a95281267ad8be3564d1c96e2553..3889830f6dd263a050cd0ef2ae2cb6996a370f13 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/store.ml +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/store.ml @@ -50,14 +50,8 @@ let load = IStore.load let readonly = IStore.readonly -type state_info = { - num_messages : Z.t; - num_ticks : Z.t; - initial_tick : Sc_rollup.Tick.t; -} - -(** Extraneous state information for the PVM *) -module StateInfo = +(** L2 blocks *) +module L2_blocks = Make_append_only_map (struct let path = ["state_info"] @@ -68,110 +62,54 @@ module StateInfo = let to_path_representation = Tezos_crypto.Block_hash.to_b58check end) (struct - type value = state_info + type value = Sc_rollup_block.t - let name = "state_info" + let name = "sc_rollup_block" - let encoding = - let open Data_encoding in - conv - (fun {num_messages; num_ticks; initial_tick} -> - (num_messages, num_ticks, initial_tick)) - (fun (num_messages, num_ticks, initial_tick) -> - {num_messages; num_ticks; initial_tick}) - (obj3 - (req "num_messages" Data_encoding.z) - (req "num_ticks" Data_encoding.z) - (req "initial_tick" Sc_rollup.Tick.encoding)) + let encoding = Sc_rollup_block.encoding end) -module StateHistoryRepr = struct - type event = { - tick : Sc_rollup.Tick.t; - block_hash : Tezos_crypto.Block_hash.t; - predecessor_hash : Tezos_crypto.Block_hash.t; - level : Raw_level.t; +(** Unaggregated messages per block *) +module Messages = struct + type info = { + predecessor : Tezos_crypto.Block_hash.t; + predecessor_timestamp : Timestamp.t; + messages : Sc_rollup.Inbox_message.t list; } - module TickMap = Map.Make (Sc_rollup.Tick) - - type value = event TickMap.t - - let event_encoding = - let open Data_encoding in - conv - (fun {tick; block_hash; predecessor_hash; level} -> - (tick, block_hash, predecessor_hash, level)) - (fun (tick, block_hash, predecessor_hash, level) -> - {tick; block_hash; predecessor_hash; level}) - (obj4 - (req "tick" Sc_rollup.Tick.encoding) - (req "block_hash" Tezos_crypto.Block_hash.encoding) - (req "predecessor_hash" Tezos_crypto.Block_hash.encoding) - (req "level" Raw_level.encoding)) - - let name = "state_history" - let encoding = let open Data_encoding in conv - TickMap.bindings - (fun bindings -> TickMap.of_seq (List.to_seq bindings)) - (Data_encoding.list (tup2 Sc_rollup.Tick.encoding event_encoding)) -end + (fun {predecessor; predecessor_timestamp; messages} -> + (predecessor, predecessor_timestamp, messages)) + (fun (predecessor, predecessor_timestamp, messages) -> + {predecessor; predecessor_timestamp; messages}) + @@ obj3 + (req "predecessor" Tezos_crypto.Block_hash.encoding) + (req "predecessor_timestamp" Timestamp.encoding) + (req + "messages" + (list @@ dynamic_size Sc_rollup.Inbox_message.encoding)) -module StateHistory = struct include - Make_mutable_value + Make_append_only_map (struct - let path = ["state_history"] + let path = ["messages"] end) - (StateHistoryRepr) - - let insert store event = - let open Lwt_result_syntax in - let open StateHistoryRepr in - let*! history = find store in - let history = - match history with - | None -> StateHistoryRepr.TickMap.empty - | Some history -> history - in - set store (TickMap.add event.tick event history) - - let event_of_largest_tick_before store tick = - let open Lwt_result_syntax in - let open StateHistoryRepr in - let*! history = find store in - match history with - | None -> return_none - | Some history -> ( - let events_before, opt_value, _ = TickMap.split tick history in - match opt_value with - | Some event -> return (Some event) - | None -> - return @@ Option.map snd @@ TickMap.max_binding_opt events_before) -end - -(** Unaggregated messages per block *) -module Messages = - Make_append_only_map - (struct - let path = ["messages"] - end) - (struct - type key = Tezos_crypto.Block_hash.t + (struct + type key = Sc_rollup.Inbox_merkelized_payload_hashes.Hash.t - let to_path_representation = Tezos_crypto.Block_hash.to_b58check - end) - (struct - type value = Sc_rollup.Inbox_message.t list + let to_path_representation = + Sc_rollup.Inbox_merkelized_payload_hashes.Hash.to_b58check + end) + (struct + type value = info - let name = "messages" + let name = "messages" - let encoding = - Data_encoding.(list @@ dynamic_size Sc_rollup.Inbox_message.encoding) - end) + let encoding = encoding + end) +end (** Inbox state for each block *) module Inboxes = @@ -180,9 +118,9 @@ module Inboxes = let path = ["inboxes"] end) (struct - type key = Tezos_crypto.Block_hash.t + type key = Sc_rollup.Inbox.Hash.t - let to_path_representation = Tezos_crypto.Block_hash.to_b58check + let to_path_representation = Sc_rollup.Inbox.Hash.to_b58check end) (struct type value = Sc_rollup.Inbox.t @@ -192,67 +130,26 @@ module Inboxes = let encoding = Sc_rollup.Inbox.encoding end) -(** Message history for the inbox at a given block *) -module Histories = - Make_append_only_map - (struct - let path = ["histories"] - end) - (struct - type key = Tezos_crypto.Block_hash.t - - let to_path_representation = Tezos_crypto.Block_hash.to_b58check - end) - (struct - type value = Sc_rollup.Inbox.History.t - - let name = "inbox_history" - - let encoding = Sc_rollup.Inbox.History.encoding - end) - -(** payloads history for the inbox at a given block *) -module Payloads_histories = - Make_append_only_map - (struct - let path = ["payloads_histories"] - end) - (struct - type key = Sc_rollup.Inbox_merkelized_payload_hashes.Hash.t - - let to_path_representation = - Sc_rollup.Inbox_merkelized_payload_hashes.Hash.to_b58check - end) - (struct - let name = "payloads_history" - - type value = Sc_rollup.Inbox_merkelized_payload_hashes.History.t - - let encoding = Sc_rollup.Inbox_merkelized_payload_hashes.History.encoding - end) - module Commitments = Make_append_only_map (struct let path = ["commitments"; "computed"] end) (struct - type key = Raw_level.t + type key = Sc_rollup.Commitment.Hash.t - let to_path_representation key = Int32.to_string @@ Raw_level.to_int32 key + let to_path_representation = Sc_rollup.Commitment.Hash.to_b58check end) (struct - type value = Sc_rollup.Commitment.t * Sc_rollup.Commitment.Hash.t + type value = Sc_rollup.Commitment.t - let name = "commitment_with_hash" + let name = "commitment" - let encoding = - Data_encoding.( - obj2 - (req "commitment" Sc_rollup.Commitment.encoding) - (req "hash" Sc_rollup.Commitment.Hash.encoding)) + let encoding = Sc_rollup.Commitment.encoding end) +(* TODO: https://gitlab.com/tezos/tezos/-/issues/4392 + Use file. *) module Last_stored_commitment_level = Make_mutable_value (struct @@ -284,24 +181,6 @@ module Commitments_published_at_level = let encoding = Raw_level.encoding end) -module Contexts = - Make_append_only_map - (struct - let path = ["contexts"] - end) - (struct - type key = Tezos_crypto.Block_hash.t - - let to_path_representation = Tezos_crypto.Block_hash.to_b58check - end) - (struct - type value = Context.hash - - let name = "context" - - let encoding = Context.hash_encoding - end) - (* Published slot headers per block hash, stored as a list of bindings from `Dal_slot_index.t` to `Dal.Slot.t`. The encoding function converts this @@ -437,15 +316,17 @@ module Dal_confirmed_slots_history = (** Confirmed DAL slots histories cache. See documentation of {Dal_slot_repr.Slots_history} for more details. *) module Dal_confirmed_slots_histories = - Make_append_only_map - (struct - let path = ["dal"; "confirmed_slots_histories_cache"] - end) - (struct - type key = Tezos_crypto.Block_hash.t + (* TODO: https://gitlab.com/tezos/tezos/-/issues/4390 + Store single history points in map instead of whole history. *) + Make_append_only_map + (struct + let path = ["dal"; "confirmed_slots_histories_cache"] + end) + (struct + type key = Tezos_crypto.Block_hash.t - let to_path_representation = Tezos_crypto.Block_hash.to_b58check - end) + let to_path_representation = Tezos_crypto.Block_hash.to_b58check + end) (struct type value = Dal.Slots_history.History_cache.t diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/store.mli b/src/proto_016_PtMumbai/bin_sc_rollup_node/store.mli index 7fd56116bf0d9edbfb453dabbda1ac0a5fd9a390..c30861a40e78f86ff6558fc773af6516ae6ec816 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/store.mli +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/store.mli @@ -47,12 +47,6 @@ type rw = Store_sigs.rw t (** Read only store {!t}. *) type ro = Store_sigs.ro t -type state_info = { - num_messages : Z.t; - num_ticks : Z.t; - initial_tick : Sc_rollup.Tick.t; -} - (** [close store] closes the store. *) val close : _ t -> unit Lwt.t @@ -62,81 +56,40 @@ val load : 'a Store_sigs.mode -> string -> 'a store Lwt.t (** [readonly store] returns a read-only version of [store]. *) val readonly : _ t -> ro -(** Extraneous state information for the PVM *) -module StateInfo : +module L2_blocks : Store_sigs.Append_only_map with type key := Tezos_crypto.Block_hash.t - and type value := state_info + and type value := Sc_rollup_block.t and type 'a store := 'a store -module StateHistoryRepr : sig - type event = { - tick : Sc_rollup.Tick.t; - block_hash : Tezos_crypto.Block_hash.t; - predecessor_hash : Tezos_crypto.Block_hash.t; - level : Raw_level.t; +(** Storage for persisting messages downloaded from the L1 node. *) +module Messages : sig + type info = { + predecessor : Tezos_crypto.Block_hash.t; + predecessor_timestamp : Timestamp.t; + messages : Sc_rollup.Inbox_message.t list; } - module TickMap : Map.S with type key := Sc_rollup.Tick.t - - type value = event TickMap.t -end - -(** [StateHistory] represents storage for the PVM state history: it is an - extension of [Store_utils.Mutable_value] whose values are lists of bindings - indexed by PVM tick numbers, and whose value contains information about the - block that the PVM was processing when generating the tick. -*) -module StateHistory : sig include - Store_sigs.Mutable_value - with type value := StateHistoryRepr.value + Store_sigs.Append_only_map + with type key := Sc_rollup.Inbox_merkelized_payload_hashes.Hash.t + and type value := info and type 'a store := 'a store - - val insert : rw -> StateHistoryRepr.event -> unit Lwt.t - - val event_of_largest_tick_before : - _ t -> Sc_rollup.Tick.t -> StateHistoryRepr.event option tzresult Lwt.t end -(** Storage for persisting messages downloaded from the L1 node, indexed by - [Tezos_crypto.Block_hash.t]. *) -module Messages : - Store_sigs.Append_only_map - with type key := Tezos_crypto.Block_hash.t - and type value := Sc_rollup.Inbox_message.t list - and type 'a store := 'a store - (** Aggregated collection of messages from the L1 inbox *) module Inboxes : Store_sigs.Append_only_map - with type key := Tezos_crypto.Block_hash.t + with type key := Sc_rollup.Inbox.Hash.t and type value := Sc_rollup.Inbox.t and type 'a store := 'a store -(** Histories from the rollup node. **) -module Histories : - Store_sigs.Append_only_map - with type key := Tezos_crypto.Block_hash.t - and type value := Sc_rollup.Inbox.History.t - and type 'a store := 'a store - -(** messages histories from the rollup node. Each history contains the messages - of one level. The store is indexed by a level in order to maintain a small - structure in memory. Only the message history of one level is fetched when - computing the proof. *) -module Payloads_histories : - Store_sigs.Append_only_map - with type key := Sc_rollup.Inbox_merkelized_payload_hashes.Hash.t - and type value := Sc_rollup.Inbox_merkelized_payload_hashes.History.t - and type 'a store := 'a store - (** Storage containing commitments and corresponding commitment hashes that the rollup node has knowledge of. *) module Commitments : Store_sigs.Append_only_map - with type key := Raw_level.t - and type value := Sc_rollup.Commitment.t * Sc_rollup.Commitment.Hash.t + with type key := Sc_rollup.Commitment.Hash.t + and type value := Sc_rollup.Commitment.t and type 'a store := 'a store (** Storage containing the inbox level of the last commitment produced by the @@ -155,13 +108,6 @@ module Commitments_published_at_level : and type value := Raw_level.t and type 'a store := 'a store -(** Storage containing the hashes of contexts retrieved from the L1 node. *) -module Contexts : - Store_sigs.Append_only_map - with type key := Tezos_crypto.Block_hash.t - and type value := Context.hash - and type 'a store := 'a store - (** Published slot headers per block hash, stored as a list of bindings from [Dal_slot_index.t] to [Dal.Slot.t]. The encoding function converts this diff --git a/src/proto_016_PtMumbai/lib_sc_rollup/sc_rollup_block.ml b/src/proto_016_PtMumbai/lib_sc_rollup/sc_rollup_block.ml new file mode 100644 index 0000000000000000000000000000000000000000..4715b8408387e3a070212960b7742dab106cde56 --- /dev/null +++ b/src/proto_016_PtMumbai/lib_sc_rollup/sc_rollup_block.ml @@ -0,0 +1,202 @@ +(*****************************************************************************) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +open Protocol +open Alpha_context + +type header = { + block_hash : Tezos_crypto.Block_hash.t; + level : Raw_level.t; + predecessor : Tezos_crypto.Block_hash.t; + commitment_hash : Sc_rollup.Commitment.Hash.t option; + previous_commitment_hash : Sc_rollup.Commitment.Hash.t; + context : Sc_rollup_context_hash.t; + inbox_witness : Sc_rollup.Inbox_merkelized_payload_hashes.Hash.t; + inbox_hash : Sc_rollup.Inbox.Hash.t; +} + +type content = { + inbox : Sc_rollup.Inbox.t; + messages : Sc_rollup.Inbox_message.t list; + commitment : Sc_rollup.Commitment.t option; +} + +type ('header, 'content) block = { + header : 'header; + content : 'content; + initial_tick : Sc_rollup.Tick.t; + num_ticks : int64; +} + +type t = (header, unit) block + +type full = (header, content) block + +let commitment_hash_opt_encoding = + let open Data_encoding in + let binary = + conv + (Option.value ~default:Sc_rollup.Commitment.Hash.zero) + (fun h -> if Sc_rollup.Commitment.Hash.(h = zero) then None else Some h) + Sc_rollup.Commitment.Hash.encoding + in + let json = option Sc_rollup.Commitment.Hash.encoding in + splitted ~binary ~json + +let header_encoding = + let open Data_encoding in + conv + (fun { + block_hash; + level; + predecessor; + commitment_hash; + previous_commitment_hash; + context; + inbox_witness; + inbox_hash; + } -> + ( block_hash, + level, + predecessor, + commitment_hash, + previous_commitment_hash, + context, + inbox_witness, + inbox_hash )) + (fun ( block_hash, + level, + predecessor, + commitment_hash, + previous_commitment_hash, + context, + inbox_witness, + inbox_hash ) -> + { + block_hash; + level; + predecessor; + commitment_hash; + previous_commitment_hash; + context; + inbox_witness; + inbox_hash; + }) + @@ obj8 + (req + "block_hash" + Tezos_crypto.Block_hash.encoding + ~description:"Tezos block hash.") + (req + "level" + Raw_level.encoding + ~description: + "Level of the block, corresponds to the level of the tezos block.") + (req + "predecessor" + Tezos_crypto.Block_hash.encoding + ~description:"Predecessor hash of the Tezos block.") + (req + "commitment_hash" + commitment_hash_opt_encoding + ~description: + "Hash of this block's commitment if any was computed for it.") + (req + "previous_commitment_hash" + Sc_rollup.Commitment.Hash.encoding + ~description: + "Previous commitment hash in the chain. If there is a commitment \ + for this block, this field contains the commitment that was \ + previously computed.") + (req + "context" + Sc_rollup_context_hash.encoding + ~description:"Hash of the layer 2 context for this block.") + (req + "inbox_witness" + Sc_rollup.Inbox_merkelized_payload_hashes.Hash.encoding + ~description: + "Witness for the inbox for this block, i.e. the Merkle hash of \ + payloads of messages.") + (req + "inbox_hash" + Sc_rollup.Inbox.Hash.encoding + ~description:"Hash of the inbox for this block.") + +let header_size = + WithExceptions.Option.get ~loc:__LOC__ + @@ Data_encoding.Binary.fixed_length header_encoding + +let content_encoding = + let open Data_encoding in + conv + (fun {inbox; messages; commitment} -> (inbox, messages, commitment)) + (fun (inbox, messages, commitment) -> {inbox; messages; commitment}) + @@ obj3 + (req + "inbox" + Sc_rollup.Inbox.encoding + ~description:"Inbox for this block.") + (req + "messages" + (list (dynamic_size Sc_rollup.Inbox_message.encoding)) + ~description:"Messages added to the inbox in this block.") + (opt + "commitment" + Sc_rollup.Commitment.encoding + ~description:"Commitment, if any is computed for this block.") + +let block_encoding header_encoding content_encoding = + let open Data_encoding in + conv + (fun {header; content; initial_tick; num_ticks} -> + (header, (content, (initial_tick, num_ticks)))) + (fun (header, (content, (initial_tick, num_ticks))) -> + {header; content; initial_tick; num_ticks}) + @@ merge_objs header_encoding + @@ merge_objs content_encoding + @@ obj2 + (req + "initial_tick" + Sc_rollup.Tick.encoding + ~description: + "Initial tick of the PVM at this block, i.e. before evaluation of \ + the messages.") + (req + "num_ticks" + int64 + ~description: + "Number of ticks produced by the evaluation of the messages in \ + this block.") + +let encoding = block_encoding header_encoding Data_encoding.unit + +let full_encoding = block_encoding header_encoding content_encoding + +let most_recent_commitment (header : header) = + Option.value header.commitment_hash ~default:header.previous_commitment_hash + +let final_tick {initial_tick; num_ticks; _} = + Sc_rollup.Tick.jump initial_tick (Z.of_int64 num_ticks) diff --git a/src/proto_016_PtMumbai/lib_sc_rollup/sc_rollup_block.mli b/src/proto_016_PtMumbai/lib_sc_rollup/sc_rollup_block.mli new file mode 100644 index 0000000000000000000000000000000000000000..7d79a246d3e61ceb95984026ca913a3cc86e1dcd --- /dev/null +++ b/src/proto_016_PtMumbai/lib_sc_rollup/sc_rollup_block.mli @@ -0,0 +1,112 @@ +(*****************************************************************************) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +open Protocol +open Alpha_context + +(** {2 Structure of layer 2 blocks} *) + +(** A layer 2 block header contains information about the inbox and commitment + with respect to a layer 1 block, but without the inbox content of + messages. *) +type header = { + block_hash : Tezos_crypto.Block_hash.t; (** Tezos block hash. *) + level : Raw_level.t; + (** Level of the block, corresponds to the level of the tezos block. *) + predecessor : Tezos_crypto.Block_hash.t; + (** Predecessor hash of the Tezos block. *) + commitment_hash : Sc_rollup.Commitment.Hash.t option; + (** Hash of this block's commitment if any was computed for it. *) + previous_commitment_hash : Sc_rollup.Commitment.Hash.t; + (** Previous commitment hash in the chain. If there is a commitment for this + block, this field contains the commitment that was previously + computed. *) + context : Sc_rollup_context_hash.t; + (** Hash of the layer 2 context for this block. *) + inbox_witness : Sc_rollup.Inbox_merkelized_payload_hashes.Hash.t; + (** Witness for the inbox for this block, i.e. the Merkle hash of payloads + of messages. *) + inbox_hash : Sc_rollup.Inbox.Hash.t; (** Hash of the inbox for this block. *) +} + +(** Contents of blocks which include the actual content of the inbox and + messages. *) +type content = { + inbox : Sc_rollup.Inbox.t; (** Inbox for this block. *) + messages : Sc_rollup.Inbox_message.t list; + (** Messages added to the inbox in this block. *) + commitment : Sc_rollup.Commitment.t option; + (** Commitment, if any is computed for this block. [header.commitment = + Some h] iff [commitment = Some c] and [hash c = h]. *) +} + +(** Block parameterized by a header and content. The parameters are here to + allow to split the header and rest of the block. *) +type ('header, 'content) block = { + header : 'header; (** Header of this block. *) + content : 'content; (** Content of the block. *) + initial_tick : Sc_rollup.Tick.t; + (** Initial tick of the PVM at this block, i.e. before evaluation of the + messages. *) + num_ticks : int64; + (** Number of ticks produced by the evaluation of the messages in this + block. *) +} + +(** The type of layer 2 blocks. This type corresponds to what is stored on disk + for L2 blocks. The contents is stored in separate tables because it can be + large to read is is not always necessary. *) +type t = (header, unit) block + +(** The type of layer 2 blocks including their content (inbox, messages, commitment). *) +type full = (header, content) block + +(** {2 Encodings} *) + +val header_encoding : header Data_encoding.t + +val header_size : int + +val content_encoding : content Data_encoding.t + +val block_encoding : + 'header Data_encoding.t -> + 'content Data_encoding.t -> + ('header, 'content) block Data_encoding.t + +val encoding : t Data_encoding.t + +val full_encoding : full Data_encoding.t + +(** {2 Helper functions} *) + +(** [most_recent_commitment header] returns the most recent commitment + information at the block of with [header]. It is either the commitment for + this block if there is one or the previous commitment otherwise. *) +val most_recent_commitment : header -> Sc_rollup.Commitment.Hash.t + +(** [final_tick block] is the final tick, after evaluation of the messages in + the [block], i.e. [block.initial_tick + block.num_ticks]. *) +val final_tick : ('a, 'b) block -> Sc_rollup.Tick.t diff --git a/src/proto_016_PtMumbai/lib_sc_rollup/sc_rollup_context_hash.ml b/src/proto_016_PtMumbai/lib_sc_rollup/sc_rollup_context_hash.ml new file mode 100644 index 0000000000000000000000000000000000000000..000536a07aa55fea3f79d2ce6506e8b777dab8bb --- /dev/null +++ b/src/proto_016_PtMumbai/lib_sc_rollup/sc_rollup_context_hash.ml @@ -0,0 +1,41 @@ +(*****************************************************************************) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +open Tezos_crypto + +include + Blake2B.Make + (Base58) + (struct + let name = "Smart_rollup_context_hash" + + let title = "A base58-check encoded hash of a Smart rollup node context" + + let b58check_prefix = "\008\209\216\166" + + let size = None + end) + +let () = Base58.check_encoded_prefix b58check_encoding "SRCo" 54 diff --git a/src/proto_016_PtMumbai/lib_sc_rollup/sc_rollup_context_hash.mli b/src/proto_016_PtMumbai/lib_sc_rollup/sc_rollup_context_hash.mli new file mode 100644 index 0000000000000000000000000000000000000000..b8abde35922ae94993464853ef4e790436d18dbb --- /dev/null +++ b/src/proto_016_PtMumbai/lib_sc_rollup/sc_rollup_context_hash.mli @@ -0,0 +1,26 @@ +(*****************************************************************************) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +include Tezos_crypto.S.HASH diff --git a/src/proto_016_PtMumbai/lib_sc_rollup/sc_rollup_services.ml b/src/proto_016_PtMumbai/lib_sc_rollup/sc_rollup_services.ml index e6886c48a873c65ca92909283027e64051ff7ea1..fd89e090352904866e51ac80b18095a9ee4dd0a0 100644 --- a/src/proto_016_PtMumbai/lib_sc_rollup/sc_rollup_services.ml +++ b/src/proto_016_PtMumbai/lib_sc_rollup/sc_rollup_services.ml @@ -402,6 +402,15 @@ module Global = struct let prefix = prefix / "block" /: Arg.block_id end) + let block = + Tezos_rpc.Service.get_service + ~description: + "Layer-2 block of the layer-2 chain with respect to a Layer 1 block \ + identifier" + ~query:Tezos_rpc.Query.empty + ~output:Sc_rollup_block.full_encoding + path + let hash = Tezos_rpc.Service.get_service ~description:"Tezos block hash of block known to the smart rollup node" diff --git a/src/proto_alpha/bin_sc_rollup_node/RPC_server.ml b/src/proto_alpha/bin_sc_rollup_node/RPC_server.ml index a58e56651068d1c7c39476cffb2641feb6e26cf3..4c44eb435dff62f7113be037758d94a03456ed19 100644 --- a/src/proto_alpha/bin_sc_rollup_node/RPC_server.ml +++ b/src/proto_alpha/bin_sc_rollup_node/RPC_server.ml @@ -30,7 +30,9 @@ open Protocol let get_head store = let open Lwt_result_syntax in let*! head = State.last_processed_head_opt store in - match head with None -> failwith "No head" | Some {hash; _} -> return hash + match head with + | None -> failwith "No head" + | Some {header = {block_hash; _}; _} -> return block_hash let get_finalized store = let open Lwt_result_syntax in @@ -51,18 +53,18 @@ let get_last_cemented (node_ctxt : _ Node_context.t) = let get_head_hash_opt store = let open Lwt_option_syntax in - let+ {hash; _} = State.last_processed_head_opt store in - hash + let+ {header = {block_hash; _}; _} = State.last_processed_head_opt store in + block_hash let get_head_level_opt store = let open Lwt_option_syntax in - let+ {level; _} = State.last_processed_head_opt store in - level + let+ {header = {level; _}; _} = State.last_processed_head_opt store in + Alpha_context.Raw_level.to_int32 level -let get_state_info_exn store block = +let get_l2_block_exn store block = let open Lwt_result_syntax in - let*! state = Store.StateInfo.get store block in - return state + let*! b = Store.L2_blocks.get store block in + return b module Slot_pages_map = struct open Protocol @@ -217,12 +219,22 @@ module Outbox_directory = Make_directory (struct end) module Common = struct + let () = + Block_directory.register0 Sc_rollup_services.Global.Block.block + @@ fun (node_ctxt, block) () () -> + let open Lwt_result_syntax in + let*! b = Node_context.get_full_l2_block node_ctxt block in + return b + let () = Block_directory.register0 Sc_rollup_services.Global.Block.num_messages @@ fun (node_ctxt, block) () () -> let open Lwt_result_syntax in - let+ state_info = get_state_info_exn node_ctxt.store block in - state_info.num_messages + let* l2_block = get_l2_block_exn node_ctxt.store block in + let*! {messages; _} = + Store.Messages.get node_ctxt.store l2_block.header.inbox_witness + in + return @@ Z.of_int (List.length messages) let () = Global_directory.register0 Sc_rollup_services.Global.sc_rollup_address @@ -252,8 +264,8 @@ module Common = struct Block_directory.register0 Sc_rollup_services.Global.Block.ticks @@ fun (node_ctxt, block) () () -> let open Lwt_result_syntax in - let+ state = get_state_info_exn node_ctxt.store block in - state.num_ticks + let+ l2_block = get_l2_block_exn node_ctxt.store block in + Z.of_int64 l2_block.num_ticks end module Make (Simulation : Simulation.S) (Batcher : Batcher.S) = struct @@ -359,14 +371,18 @@ module Make (Simulation : Simulation.S) (Batcher : Batcher.S) = struct Global_directory.register0 Sc_rollup_services.Global.last_stored_commitment @@ fun node_ctxt () () -> let open Lwt_result_syntax in - let*! commitment_with_hash = - Commitment.last_commitment_with_hash - (module Store.Last_stored_commitment_level) - node_ctxt.store + let*! res = + let open Lwt_option_syntax in + let* head = State.last_processed_head_opt node_ctxt.store in + let commitment_hash = + Sc_rollup_block.most_recent_commitment head.header + in + let*! commitment = + Store.Commitments.get node_ctxt.store commitment_hash + in + return (commitment, commitment_hash, None) in - return - (commitment_with_hash - |> Option.map (fun (commitment, hash) -> (commitment, hash, None))) + return res let () = Local_directory.register0 Sc_rollup_services.Local.last_published_commitment @@ -519,15 +535,25 @@ module Make (Simulation : Simulation.S) (Batcher : Batcher.S) = struct | None -> return (Sc_rollup_services.Included (info, inbox_info)) | Some commitment_level -> ( - let*! commitment = - Store.Commitments.find node_ctxt.store commitment_level + let*! block = + State.hash_of_level + node_ctxt.store + (Alpha_context.Raw_level.to_int32 commitment_level) in - match commitment with + let*! block = + Store.L2_blocks.find node_ctxt.store block + in + match block with | None -> (* Commitment not computed yet for inbox *) return (Sc_rollup_services.Included (info, inbox_info)) - | Some (commitment, commitment_hash) -> ( + | Some block -> ( + let commitment_hash = + WithExceptions.Option.get + ~loc:__LOC__ + block.header.commitment_hash + in (* Commitment computed *) let*! published_at = Store.Commitments_published_at_level.find @@ -541,6 +567,11 @@ module Make (Simulation : Simulation.S) (Batcher : Batcher.S) = struct (Sc_rollup_services.Included (info, inbox_info)) | Some published_at -> (* Commitment published *) + let*! commitment = + Store.Commitments.get + node_ctxt.store + commitment_hash + in let commitment_info = Sc_rollup_services. {commitment; commitment_hash; published_at} diff --git a/src/proto_alpha/bin_sc_rollup_node/commitment.ml b/src/proto_alpha/bin_sc_rollup_node/commitment.ml index 40019e6dd72693dccf4d89732750bace15adcae1..fae9c06423e853f8e830932e8e3159ae1a80114e 100644 --- a/src/proto_alpha/bin_sc_rollup_node/commitment.ml +++ b/src/proto_alpha/bin_sc_rollup_node/commitment.ml @@ -44,249 +44,178 @@ open Protocol open Alpha_context -module type Mutable_level_store = - Store_sigs.Mutable_value - with type value := Raw_level.t - and type 'a store := 'a Store.store - -(* We persist the number of ticks to be included in the - next commitment on disk, in a map that is indexed by - inbox level. Note that we do not risk to increase - these counters when the wrong branch is tracked by the rollup - node, as only finalized heads are processed to build commitments. -*) -module Number_of_ticks : - Store_sigs.Append_only_map - with type 'a store = 'a Store.store - and type key = Raw_level.t - and type value = Z.t = - Store.Make_append_only_map - (struct - let path = ["commitments"; "in_progress"; "number_of_ticks"] - end) - (struct - type key = Raw_level.t - - let to_path_representation key = Int32.to_string @@ Raw_level.to_int32 key - end) - (struct - type value = Z.t - - let name = "ticks" - - let encoding = Data_encoding.z - end) +let add_level level increment = + (* We only use this function with positive increments so it is safe *) + if increment < 0 then invalid_arg "Commitment.add_level negative increment" ; + Raw_level.Internal_for_tests.add level increment let sc_rollup_commitment_period node_ctxt = - Int32.of_int - @@ node_ctxt.Node_context.protocol_constants.parametric.sc_rollup - .commitment_period_in_blocks + node_ctxt.Node_context.protocol_constants.parametric.sc_rollup + .commitment_period_in_blocks let sc_rollup_challenge_window node_ctxt = - Int32.of_int - node_ctxt.Node_context.protocol_constants.parametric.sc_rollup - .challenge_window_in_blocks - -let last_commitment_level (module Last_commitment_level : Mutable_level_store) - store = - Last_commitment_level.find store - -let last_commitment_with_hash - (module Last_commitment_level : Mutable_level_store) store = - let open Lwt_option_syntax in - let* last_commitment_level = - last_commitment_level (module Last_commitment_level) store - in - let*! commitment_with_hash = - Store.Commitments.get store last_commitment_level - in - return commitment_with_hash + node_ctxt.Node_context.protocol_constants.parametric.sc_rollup + .challenge_window_in_blocks let next_lcc_level node_ctxt = - Environment.wrap_tzresult @@ Raw_level.of_int32 - @@ Int32.add - (Raw_level.to_int32 node_ctxt.Node_context.lcc.level) - (sc_rollup_commitment_period node_ctxt) - + add_level + node_ctxt.Node_context.lcc.level + (sc_rollup_commitment_period node_ctxt) + +(** Returns the next level for which a commitment can be published, i.e. the + level that is [commitment_period] blocks after the last published one. It + returns [None] if this level is not finalized because we only publish + commitments for inbox of finalized L1 blocks. *) let next_publishable_level node_ctxt = + let open Lwt_option_syntax in let lpc_level = match node_ctxt.Node_context.lpc with | None -> node_ctxt.genesis_info.level | Some lpc -> lpc.inbox_level in - Environment.wrap_tzresult @@ Raw_level.of_int32 - @@ Int32.add - (Raw_level.to_int32 lpc_level) - (sc_rollup_commitment_period node_ctxt) - -let next_commitment_level node_ctxt - (module Last_commitment_level : Mutable_level_store) = - let open Lwt_syntax in - let+ last_commitment_level_opt = - last_commitment_level - (module Last_commitment_level) - node_ctxt.Node_context.store - in - let last_commitment_level = - Option.value last_commitment_level_opt ~default:node_ctxt.genesis_info.level - in - Raw_level.of_int32 - @@ Int32.add - (Raw_level.to_int32 last_commitment_level) - (sc_rollup_commitment_period node_ctxt) - -let last_commitment_hash node_ctxt - (module Last_commitment_level : Mutable_level_store) = - let open Lwt_syntax in - let+ last_commitment = - last_commitment_with_hash - (module Last_commitment_level) - node_ctxt.Node_context.store - in - match last_commitment with - | Some (_commitment, hash) -> hash - | None -> node_ctxt.genesis_info.Sc_rollup.Commitment.commitment_hash - -let must_store_commitment node_ctxt current_level = - let open Lwt_result_syntax in - let+ next_commitment_level = - next_commitment_level node_ctxt (module Store.Last_stored_commitment_level) + let next_level = + add_level lpc_level (sc_rollup_commitment_period node_ctxt) in + let* finalized_level = State.get_finalized_head_opt node_ctxt.store in + if Raw_level.(of_int32_exn finalized_level.level < next_level) then fail + else return next_level - Raw_level.equal current_level next_commitment_level - -let update_last_stored_commitment (node_ctxt : _ Node_context.t) - (commitment : Sc_rollup.Commitment.t) = - let open Lwt_syntax in - let commitment_hash = Sc_rollup.Commitment.hash_uncarbonated commitment in - let inbox_level = commitment.inbox_level in - (* Do not change the order of these two operations. This guarantees that - whenever `Store.Last_stored_commitment_level.get` returns `Some hash`, - then the call to `Store.Commitments.get hash` will succeed. - *) - let* () = - Store.Commitments.add - node_ctxt.store - inbox_level - (commitment, commitment_hash) - in - let* () = - Store.Last_stored_commitment_level.set node_ctxt.store inbox_level - in - let* () = Commitment_event.commitment_stored commitment_hash commitment in - if commitment.inbox_level <= node_ctxt.lcc.level then - Commitment_event.commitment_will_not_be_published - node_ctxt.lcc.level - commitment - else return () +let next_commitment_level node_ctxt last_commitment_level = + add_level last_commitment_level (sc_rollup_commitment_period node_ctxt) module Make (PVM : Pvm.S) : Commitment_sig.S with module PVM = PVM = struct module PVM = PVM - let build_commitment node_ctxt block_hash = - let open Lwt_result_syntax in - let lsc = - (module Store.Last_stored_commitment_level : Mutable_level_store) - in - let*! predecessor = last_commitment_hash node_ctxt lsc in - let* inbox_level = - Lwt.map Environment.wrap_tzresult @@ next_commitment_level node_ctxt lsc + let tick_of_level (node_ctxt : _ Node_context.t) inbox_level = + let open Lwt_syntax in + let* block_hash = + State.hash_of_level node_ctxt.store (Raw_level.to_int32 inbox_level) in - let* ctxt = Node_context.checkout_context node_ctxt block_hash in + let+ block = Store.L2_blocks.get node_ctxt.store block_hash in + Sc_rollup_block.final_tick block + + let build_commitment (node_ctxt : _ Node_context.t) + (prev_commitment : Sc_rollup.Commitment.Hash.t) ~prev_commitment_level + ~inbox_level ctxt = + let open Lwt_result_syntax in let*! pvm_state = PVM.State.find ctxt in - let* compressed_state = + let*? pvm_state = match pvm_state with - | Some pvm_state -> - let*! hash = PVM.state_hash pvm_state in - return hash + | Some pvm_state -> Ok pvm_state | None -> - failwith - "PVM state for block hash not available %s" - (Tezos_crypto.Block_hash.to_string block_hash) + error_with + "PVM state for commitment at level %a is not available" + Raw_level.pp + inbox_level + in + let*! compressed_state = PVM.state_hash pvm_state in + let*! tick = PVM.get_tick pvm_state in + let*! prev_commitment_tick = + tick_of_level node_ctxt prev_commitment_level + in + let number_of_ticks = + Sc_rollup.Tick.distance tick prev_commitment_tick + |> Z.to_int64 |> Sc_rollup.Number_of_ticks.of_value in - let*! number_of_ticks = Number_of_ticks.get node_ctxt.store inbox_level in - let+ number_of_ticks = - match - Sc_rollup.Number_of_ticks.of_value @@ Z.to_int64 number_of_ticks - with + let*? number_of_ticks = + match number_of_ticks with | Some number_of_ticks -> if number_of_ticks = Sc_rollup.Number_of_ticks.zero then - failwith "A 0-tick commitment is impossible" - else return number_of_ticks - | None -> - failwith "Invalid number of ticks %s" (Z.to_string number_of_ticks) + error_with "A 0-tick commitment is impossible" + else Ok number_of_ticks + | None -> error_with "Invalid number of ticks for commitment" in - Sc_rollup.Commitment. - {predecessor; inbox_level; number_of_ticks; compressed_state} - - let store_commitment_if_necessary node_ctxt current_level block_hash = + return + Sc_rollup.Commitment. + { + predecessor = prev_commitment; + inbox_level; + number_of_ticks; + compressed_state; + } + + let create_commitment_if_necessary (node_ctxt : _ Node_context.t) ~predecessor + current_level ctxt = let open Lwt_result_syntax in - let* must_store_commitment = - Lwt.map Environment.wrap_tzresult - @@ must_store_commitment node_ctxt current_level - in - if must_store_commitment then - let*! () = Commitment_event.compute_commitment block_hash current_level in - let* commitment = build_commitment node_ctxt block_hash in - let*! () = update_last_stored_commitment node_ctxt commitment in - return_unit - else return_unit + if Raw_level.(current_level = node_ctxt.genesis_info.level) then + let+ genesis_commitment = + Plugin.RPC.Sc_rollup.commitment + node_ctxt.cctxt + (node_ctxt.cctxt#chain, `Head 0) + node_ctxt.rollup_address + node_ctxt.genesis_info.commitment_hash + in + Some genesis_commitment + else + let* last_commitment_hash = + let*! pred = Store.L2_blocks.find node_ctxt.store predecessor in + match pred with + | None -> + failwith "Missing block %a" Tezos_crypto.Block_hash.pp predecessor + | Some pred -> + return (Sc_rollup_block.most_recent_commitment pred.header) + in + let*! last_commitment = + Store.Commitments.get node_ctxt.store last_commitment_hash + in + let next_commitment_level = + next_commitment_level node_ctxt last_commitment.inbox_level + in + if Raw_level.(current_level = next_commitment_level) then + let*! () = Commitment_event.compute_commitment current_level in + let+ commitment = + build_commitment + node_ctxt + last_commitment_hash + ~prev_commitment_level:last_commitment.inbox_level + ~inbox_level:current_level + ctxt + in + Some commitment + else return_none - let update_ticks (node_ctxt : _ Node_context.t) current_level block_hash = + let process_head (node_ctxt : _ Node_context.t) ~predecessor Layer1.{level; _} + ctxt = let open Lwt_result_syntax in - let*! last_stored_commitment_level_opt = - last_commitment_level - (module Store.Last_stored_commitment_level) - node_ctxt.store - in - let last_stored_commitment_level = - Option.value - ~default:node_ctxt.genesis_info.level - last_stored_commitment_level_opt - in - let*! previous_level_num_ticks = - match Raw_level.pred current_level with - | None -> - (* This happens if the current_level is zero: it is safe to assume - that there are 0 ticks computed so far. *) - Lwt.return Z.zero - | Some level -> - if Raw_level.(level = last_stored_commitment_level) then - (* We are at the first level of a new commitment, so the initial amount - of ticks should be 0. *) - Lwt.return Z.zero - else if Raw_level.(level < node_ctxt.genesis_info.level) then - (* If the previous level was before the genesis level, then the - number of ticks at that level should be 0. *) - Lwt.return Z.zero - else - (* Otherwise we need to increment the number of ticks from the number - of ticks for the previous level. The number of ticks for such a - level should be in the store, otherwise the state of the rollup node - is corrupted. *) - Number_of_ticks.get node_ctxt.store level + let current_level = Raw_level.of_int32_exn level in + let* commitment = + create_commitment_if_necessary node_ctxt ~predecessor current_level ctxt in - let*! {num_ticks; _} = Store.StateInfo.get node_ctxt.store block_hash in - Number_of_ticks.add - node_ctxt.store - current_level - (Z.add previous_level_num_ticks num_ticks) + match commitment with + | None -> return_none + | Some commitment -> + let commitment_hash = + Sc_rollup.Commitment.hash_uncarbonated commitment + in + let*! () = + Store.Commitments.add node_ctxt.store commitment_hash commitment + in + return_some commitment_hash - let process_head (node_ctxt : _ Node_context.t) Layer1.{level; hash} = - let open Lwt_result_syntax in - let current_level = Raw_level.of_int32_exn level in - let*! () = update_ticks node_ctxt current_level hash in - store_commitment_if_necessary node_ctxt current_level hash + let block_of_known_level store level = + let open Lwt_option_syntax in + let* head = State.last_processed_head_opt store in + if Raw_level.(head.header.level < level) then + (* Level is not known yet *) + fail + else + let*! block_hash = State.hash_of_level store (Raw_level.to_int32 level) in + Store.L2_blocks.find store block_hash let get_commitment_and_publish ~check_lcc_hash ({store; _} as node_ctxt : _ Node_context.t) next_level_to_publish = let open Lwt_result_syntax in - let*! commitment = Store.Commitments.find store next_level_to_publish in + let*! commitment = + let open Lwt_option_syntax in + let* block = block_of_known_level store next_level_to_publish in + let*? commitment_hash = block.header.commitment_hash in + Store.Commitments.find node_ctxt.store commitment_hash + in match commitment with | None -> (* Commitment not available *) return_unit - | Some (commitment, _commitment_hash) -> ( + | Some commitment -> ( let* () = if check_lcc_hash then let open Lwt_result_syntax in @@ -332,11 +261,14 @@ module Make (PVM : Pvm.S) : Commitment_sig.S with module PVM = PVM = struct (* Check level of next publishable commitment and avoid publishing if it is on or before the last cemented commitment. *) - let*? next_lcc_level = next_lcc_level node_ctxt in - let*? next_publishable_level = next_publishable_level node_ctxt in - let check_lcc_hash, level_to_publish = - if Raw_level.(next_publishable_level < next_lcc_level) then - (* + let next_lcc_level = next_lcc_level node_ctxt in + let*! next_publishable_level = next_publishable_level node_ctxt in + match next_publishable_level with + | None -> return_unit + | Some next_publishable_level -> + let check_lcc_hash, level_to_publish = + if Raw_level.(next_publishable_level < next_lcc_level) then + (* This situation can happen if the rollup node has been shutdown and the rollup has been progressing in the @@ -350,10 +282,10 @@ module Make (PVM : Pvm.S) : Commitment_sig.S with module PVM = PVM = struct that's an invalid commitment. *) - (true, next_lcc_level) - else (false, next_publishable_level) - in - get_commitment_and_publish node_ctxt level_to_publish ~check_lcc_hash + (true, next_lcc_level) + else (false, next_publishable_level) + in + get_commitment_and_publish node_ctxt level_to_publish ~check_lcc_hash let earliest_cementing_level node_ctxt commitment_hash = let open Lwt_option_syntax in @@ -362,9 +294,7 @@ module Make (PVM : Pvm.S) : Commitment_sig.S with module PVM = PVM = struct node_ctxt.Node_context.store commitment_hash in - Int32.add - (Raw_level.to_int32 published_at_level) - (sc_rollup_challenge_window node_ctxt) + add_level published_at_level (sc_rollup_challenge_window node_ctxt) let can_be_cemented node_ctxt earliest_cementing_level head_level commitment_hash = @@ -393,16 +323,18 @@ module Make (PVM : Pvm.S) : Commitment_sig.S with module PVM = PVM = struct let* _hash = Injector.add_pending_operation ~source cement_operation in return_unit - let cement_commitment_if_possible node_ctxt Layer1.{level = head_level; _} = + let cement_commitment_if_possible ({Node_context.store; _} as node_ctxt) + Layer1.{level = head_level; _} = let open Lwt_result_syntax in - let*? next_level_to_cement = next_lcc_level node_ctxt in - let*! commitment_with_hash = - Store.Commitments.find node_ctxt.store next_level_to_cement - in - match commitment_with_hash with - (* If `commitment_with_hash` is defined, the commitment to be cemented has - been stored but not necessarily published by the rollup node. *) - | Some (_commitment, commitment_hash) -> ( + let next_level_to_cement = next_lcc_level node_ctxt in + let*! block = block_of_known_level store next_level_to_cement in + match block with + | None | Some {header = {commitment_hash = None; _}; _} -> + (* Commitment not available *) + return_unit + | Some {header = {commitment_hash = Some commitment_hash; _}; _} -> ( + (* If `commitment_hash` is defined, the commitment to be cemented has + been stored but not necessarily published by the rollup node. *) let*! earliest_cementing_level = earliest_cementing_level node_ctxt commitment_hash in @@ -415,13 +347,12 @@ module Make (PVM : Pvm.S) : Commitment_sig.S with module PVM = PVM = struct can_be_cemented node_ctxt earliest_cementing_level - head_level + (Raw_level.of_int32_exn head_level) commitment_hash in if green_flag then cement_commitment node_ctxt commitment_hash - else return () - | None -> return ()) - | None -> return () + else return_unit + | None -> return_unit) let start () = Commitment_event.starting () end diff --git a/src/proto_alpha/bin_sc_rollup_node/commitment.mli b/src/proto_alpha/bin_sc_rollup_node/commitment.mli index 724914264ebb57972317d453b7880483c24d05e3..cb6c364d87cd6952c2306c2b3b520cc7089420e5 100644 --- a/src/proto_alpha/bin_sc_rollup_node/commitment.mli +++ b/src/proto_alpha/bin_sc_rollup_node/commitment.mli @@ -39,26 +39,4 @@ commitment that was not published already. *) -open Protocol.Alpha_context - -module type Mutable_level_store = - Store_sigs.Mutable_value - with type value := Raw_level.t - and type 'a store := 'a Store.store - -(** [last_commitment_with_hash (module Last_level_module: Mutable_level_store) store] - returns the last commitment and relative hash - stored according to the value of level indicated by - [module Last_level_module]. If no commitment has been stored for the - level indicated by [module Last_level_module], then None is returned. - Two possible implementations for [module Last_level_module] are - [Store.Last_published_commitment_level] and - [Store.Last_stored_commitment_level]. - *) - -val last_commitment_with_hash : - (module Mutable_level_store) -> - _ Store.t -> - (Sc_rollup.Commitment.t * Sc_rollup.Commitment.Hash.t) option Lwt.t - module Make (PVM : Pvm.S) : Commitment_sig.S with module PVM = PVM diff --git a/src/proto_alpha/bin_sc_rollup_node/commitment_event.ml b/src/proto_alpha/bin_sc_rollup_node/commitment_event.ml index 1f8e2c05e6a60c9de1fbbe43efd78350790c1e66..332a4e6013b68ab6ee44583efbd1cab13a5e7ad5 100644 --- a/src/proto_alpha/bin_sc_rollup_node/commitment_event.ml +++ b/src/proto_alpha/bin_sc_rollup_node/commitment_event.ml @@ -75,13 +75,11 @@ module Simple = struct ("level", Raw_level.encoding) let compute_commitment = - declare_2 + declare_1 ~section ~name:"sc_rollup_node_commitment_process_head" - ~msg: - "Computing and storing new commitment for head {head} at level {level}" + ~msg:"Computing and storing new commitment for level {level}" ~level:Notice - ("head", Tezos_crypto.Block_hash.encoding) ("level", Raw_level.encoding) let commitment_parent_is_not_lcc = @@ -144,8 +142,7 @@ let commitment_stored = emit_commitment_event Simple.commitment_stored let last_cemented_commitment_updated head level = Simple.(emit last_cemented_commitment_updated (head, level)) -let compute_commitment head level = - Simple.(emit compute_commitment (head, level)) +let compute_commitment level = Simple.(emit compute_commitment level) let commitment_parent_is_not_lcc level predecessor_hash lcc_hash = Simple.(emit commitment_parent_is_not_lcc (level, predecessor_hash, lcc_hash)) diff --git a/src/proto_alpha/bin_sc_rollup_node/commitment_event.mli b/src/proto_alpha/bin_sc_rollup_node/commitment_event.mli index bdca58229142ddfab1deaa3e643a7a1bf3d2d99b..600fc2d4b7d3bbd466a932abf71829a08f5f599e 100644 --- a/src/proto_alpha/bin_sc_rollup_node/commitment_event.mli +++ b/src/proto_alpha/bin_sc_rollup_node/commitment_event.mli @@ -61,7 +61,6 @@ val commitment_parent_is_not_lcc : Sc_rollup.Commitment.Hash.t -> unit Lwt.t -(** [compute_commitment hash level] emits the event that a new commitment is - being computed and stored for the block of the given [hash] and at the given - [level]. *) -val compute_commitment : Tezos_crypto.Block_hash.t -> Raw_level.t -> unit Lwt.t +(** [compute_commitment level] emits the event that a new commitment is being + computed and stored for the block at the given [level]. *) +val compute_commitment : Raw_level.t -> unit Lwt.t diff --git a/src/proto_alpha/bin_sc_rollup_node/commitment_sig.ml b/src/proto_alpha/bin_sc_rollup_node/commitment_sig.ml index eeacadea5cf06683a23a232373fae6cb0ce5bd2e..0b5faa4c6cf25ba7c986befaa630c912ec858fab 100644 --- a/src/proto_alpha/bin_sc_rollup_node/commitment_sig.ml +++ b/src/proto_alpha/bin_sc_rollup_node/commitment_sig.ml @@ -42,12 +42,17 @@ module type S = sig module PVM : Pvm.S - (** [process_head node_ctxt head] checks whether a new commitment needs to be - computed and stored, by looking at the level of [head] and checking - whether it is a multiple of `Commitment.sc_rollup_commitment_period` - levels away from [node_ctxt.initial_level]. It uses the functionalities of - [PVM] to compute the hash of to be included in the commitment. *) - val process_head : Node_context.rw -> Layer1.head -> unit tzresult Lwt.t + (** [process_head node_ctxt ~predecessor head ctxt] builds a new commitment if + needed, by looking at the level of [head] and checking whether it is a + multiple of `Commitment.sc_rollup_commitment_period` levels away from + [node_ctxt.initial_level]. It uses the functionalities of [PVM] to compute + the hash of to be included in the commitment. *) + val process_head : + Node_context.rw -> + predecessor:Tezos_crypto.Block_hash.t -> + Layer1.head -> + Context.rw -> + Protocol.Alpha_context.Sc_rollup.Commitment.Hash.t option tzresult Lwt.t (** [publish_commitment node_ctxt] publishes the earliest commitment stored in [store] that has not been published yet, unless its inbox level diff --git a/src/proto_alpha/bin_sc_rollup_node/context.ml b/src/proto_alpha/bin_sc_rollup_node/context.ml index 4c711cc857d1b1b9474038c656bced9d58f42759..0df91a965c46de08830287dea19dc6b3141a4f7d 100644 --- a/src/proto_alpha/bin_sc_rollup_node/context.ml +++ b/src/proto_alpha/bin_sc_rollup_node/context.ml @@ -62,28 +62,21 @@ type ro = [`Read] t type commit = IStore.commit -type hash = IStore.hash +type hash = Sc_rollup_context_hash.t type path = string list -let hash_encoding = - let open Data_encoding in - conv - (fun h -> IStore.Hash.to_raw_string h |> Bytes.unsafe_of_string) - (fun b -> Bytes.unsafe_to_string b |> IStore.Hash.unsafe_of_raw_string) - (Fixed.bytes IStore.Hash.hash_size) +let () = assert (Sc_rollup_context_hash.size = IStore.Hash.hash_size) -let hash_to_raw_string = IStore.Hash.to_raw_string +let hash_to_istore_hash h = + Sc_rollup_context_hash.to_string h |> IStore.Hash.unsafe_of_raw_string -let pp_hash fmt h = - IStore.Hash.to_raw_string h - |> Hex.of_string |> Hex.show |> Format.pp_print_string fmt +let istore_hash_to_hash h = + IStore.Hash.to_raw_string h |> Sc_rollup_context_hash.of_string_exn let load : type a. a mode -> string -> a raw_index Lwt.t = - fun mode data_dir -> + fun mode path -> let open Lwt_syntax in - let open Configuration in - let path = default_context_dir data_dir in let readonly = match mode with Read_only -> true | Read_write -> false in let+ repo = IStore.Repo.v (Irmin_pack.config ~readonly path) in {path; repo} @@ -99,11 +92,11 @@ let raw_commit ?(message = "") index tree = let commit ?message ctxt = let open Lwt_syntax in let+ commit = raw_commit ?message ctxt.index ctxt.tree in - IStore.Commit.hash commit + IStore.Commit.hash commit |> istore_hash_to_hash let checkout index key = let open Lwt_syntax in - let* o = IStore.Commit.of_hash index.repo key in + let* o = IStore.Commit.of_hash index.repo (hash_to_istore_hash key) in match o with | None -> return_none | Some commit -> diff --git a/src/proto_alpha/bin_sc_rollup_node/context.mli b/src/proto_alpha/bin_sc_rollup_node/context.mli index 2f5d4de887ae3b6e19241131c8b4831b163155ae..76a0f70c2924f459c71591a70118b672d621d814 100644 --- a/src/proto_alpha/bin_sc_rollup_node/context.mli +++ b/src/proto_alpha/bin_sc_rollup_node/context.mli @@ -49,22 +49,12 @@ type ro = [`Read] t (** A context hash is the hash produced when the data of the context is committed to disk, i.e. the {!commit} hash. *) -type hash +type hash = Sc_rollup_context_hash.t (** The type of commits for the context. *) type commit -(** [hash_encoding] is the encoding for context hashes, of type {!hash}. *) -val hash_encoding : hash Data_encoding.t - -(** [hash_to_raw_string h] is the raw string representation for the hash [h]. *) -val hash_to_raw_string : hash -> string - -(** [pp_hash fmt h] prints the hash [h] in hexadecimal notation on the formatter - [fmt]. *) -val pp_hash : Format.formatter -> hash -> unit - -(** [load data_dir] initializes from disk a context using the [data_dir]. *) +(** [load path] initializes from disk a context from [path]. *) val load : 'a mode -> string -> 'a index Lwt.t (** [index context] is the repository of the context [context]. *) diff --git a/src/proto_alpha/bin_sc_rollup_node/daemon.ml b/src/proto_alpha/bin_sc_rollup_node/daemon.ml index bce042ef1f2f882f35bd5cba7c5ce73f5c639675..8f23ed2c27403d87c290bd10b38f8758372a9dad 100644 --- a/src/proto_alpha/bin_sc_rollup_node/daemon.ml +++ b/src/proto_alpha/bin_sc_rollup_node/daemon.ml @@ -190,7 +190,6 @@ module Make (PVM : Pvm.S) = struct in let*! () = Daemon_event.head_processing hash level ~finalized:true in let* () = process_l1_block_operations ~finalized:true node_ctxt block in - let* () = Components.Commitment.process_head node_ctxt block in let*! () = State.mark_finalized_head node_ctxt.store block in return_unit @@ -198,17 +197,60 @@ module Make (PVM : Pvm.S) = struct = let open Lwt_result_syntax in let*! () = Daemon_event.head_processing hash level ~finalized:false in - let* ctxt = Inbox.process_head node_ctxt head in + let*! () = State.set_block_level_and_hash node_ctxt.store head in + let* inbox_hash, inbox, inbox_witness, messages, ctxt = + Inbox.process_head node_ctxt head + in let* () = when_ (Node_context.dal_enabled node_ctxt) @@ fun () -> Dal_slots_tracker.process_head node_ctxt head in - let*! () = State.set_block_level_and_hash node_ctxt.store head in let* () = process_l1_block_operations ~finalized:false node_ctxt head in (* Avoid storing and publishing commitments if the head is not final. *) (* Avoid triggering the pvm execution if this has been done before for this head. *) - let* () = Components.Interpreter.process_head node_ctxt ctxt head in + let* ctxt, _num_messages, num_ticks, initial_tick = + Components.Interpreter.process_head node_ctxt ctxt head (inbox, messages) + in + let*! context_hash = Context.commit ctxt in + let* {Layer1.hash = predecessor; _} = + Layer1.get_predecessor node_ctxt.l1_ctxt head + in + let* commitment_hash = + Components.Commitment.process_head node_ctxt ~predecessor head ctxt + in + let level = Raw_level.of_int32_exn level in + let* previous_commitment_hash = + if level = node_ctxt.genesis_info.Sc_rollup.Commitment.level then + (* Previous commitment for rollup genesis is itself. *) + return node_ctxt.genesis_info.Sc_rollup.Commitment.commitment_hash + else + let*! pred = Store.L2_blocks.find node_ctxt.store predecessor in + match pred with + | None -> + failwith + "Missing L2 predecessor %a for previous commitment" + Tezos_crypto.Block_hash.pp + predecessor + | Some pred -> + return (Sc_rollup_block.most_recent_commitment pred.header) + in + let header = + Sc_rollup_block. + { + block_hash = hash; + level; + predecessor; + commitment_hash; + previous_commitment_hash; + context = context_hash; + inbox_witness; + inbox_hash; + } + in + let l2_block = + Sc_rollup_block.{header; content = (); num_ticks; initial_tick} + in let* finalized_block, _ = Layer1.nth_predecessor node_ctxt.l1_ctxt @@ -216,14 +258,16 @@ module Make (PVM : Pvm.S) = struct head in let* () = processed_finalized_block node_ctxt finalized_block in - let*! () = State.mark_processed_head node_ctxt.store head in + let*! () = State.save_l2_block node_ctxt.store l2_block in (* Publishing a commitment when one is available does not depend on the state of the current head. *) let* () = Components.Commitment.publish_commitment node_ctxt in let* () = Components.Commitment.cement_commitment_if_possible node_ctxt head in - let*! () = Daemon_event.new_head_processed hash level in + let*! () = + Daemon_event.new_head_processed hash (Raw_level.to_int32 level) + in return_unit let notify_injector {Node_context.l1_ctxt; _} new_head @@ -253,7 +297,13 @@ module Make (PVM : Pvm.S) = struct in let old_head = match old_head with - | Some old_head -> `Head old_head + | Some h -> + `Head + Layer1. + { + hash = h.header.block_hash; + level = Raw_level.to_int32 h.header.level; + } | None -> (* if no head has been processed yet, we want to handle all blocks since, and including, the rollup origination. *) @@ -475,7 +525,9 @@ let run ~data_dir (configuration : Configuration.t) let*! store = Store.load Read_write Configuration.(default_storage_dir data_dir) in - let*! context = Context.load Read_write data_dir in + let*! context = + Context.load Read_write (Configuration.default_context_dir data_dir) + in let* l1_ctxt, kind = Layer1.start configuration cctxt store in let* node_ctxt = Node_context.init diff --git a/src/proto_alpha/bin_sc_rollup_node/fueled_pvm.ml b/src/proto_alpha/bin_sc_rollup_node/fueled_pvm.ml index d5b570a5e8d6c8e9fe7b7ff037a57f520c6acdbe..8b4b13c2c34d559862438a001e7604b7d65ab8b4 100644 --- a/src/proto_alpha/bin_sc_rollup_node/fueled_pvm.ml +++ b/src/proto_alpha/bin_sc_rollup_node/fueled_pvm.ml @@ -36,16 +36,16 @@ module type S = sig type eval_result = {state : PVM.state; remaining_fuel : fuel; num_ticks : Z.t} - (** [eval_block_inbox ~fuel node_ctxt block_hash state] evaluates the - [messages] for the inbox of block [block_hash] in the given [state] of the - PVM and returns the evaluation results containing the new state, the - number of messages, the inbox level and the remaining fuel. *) + (** [eval_block_inbox ~fuel node_ctxt (inbox, messages) state] evaluates the + [messages] for the [inbox] in the given [state] of the PVM and returns the + evaluation results containing the new state, the number of messages, the + inbox level and the remaining fuel. *) val eval_block_inbox : fuel:fuel -> _ Node_context.t -> - Tezos_crypto.Block_hash.t -> + Sc_rollup.Inbox.t * Sc_rollup.Inbox_message.t list -> PVM.state -> - (PVM.state * Z.t * Raw_level.t * fuel) Node_context.delayed_write tzresult + (PVM.state * int * Raw_level.t * fuel) Node_context.delayed_write tzresult Lwt.t (** [eval_messages ?reveal_map ~fuel node_ctxt ~message_counter_offset state @@ -341,33 +341,26 @@ module Make (PVM : Pvm.S) = struct (state, fuel) messages - let eval_block_inbox ~fuel (Node_context.{store; _} as node_ctxt) hash - (state : PVM.state) : - (PVM.state * Z.t * Raw_level.t * fuel) Node_context.delayed_write + let eval_block_inbox ~fuel node_ctxt (inbox, messages) (state : PVM.state) : + (PVM.state * int * Raw_level.t * fuel) Node_context.delayed_write tzresult Lwt.t = - let open Lwt_result_syntax in let open Delayed_write_monad.Lwt_result_syntax in (* Obtain inbox and its messages for this block. *) - let*! inbox = Store.Inboxes.find store hash in - match inbox with - | None -> failwith "There is no empty inbox" - | Some inbox -> - let inbox_level = Inbox.inbox_level inbox in - let*! messages = Store.Messages.get store hash in - let num_messages = List.length messages |> Z.of_int in - (* Evaluate all the messages for this level. *) - let>* state, fuel = - eval_messages - ~reveal_map:None - ~fuel - node_ctxt - ~message_counter_offset:0 - state - inbox_level - messages - in - return (state, num_messages, inbox_level, fuel) + let inbox_level = Inbox.inbox_level inbox in + let num_messages = List.length messages in + (* Evaluate all the messages for this level. *) + let>* state, fuel = + eval_messages + ~reveal_map:None + ~fuel + node_ctxt + ~message_counter_offset:0 + state + inbox_level + messages + in + return (state, num_messages, inbox_level, fuel) let eval_messages ?reveal_map ~fuel node_ctxt ~message_counter_offset state inbox_level messages = diff --git a/src/proto_alpha/bin_sc_rollup_node/inbox.ml b/src/proto_alpha/bin_sc_rollup_node/inbox.ml index 8f856e029fd150f4ecbee597539776be8cbd2087..ce186d891ef87c69098606b156190d78ac72edd7 100644 --- a/src/proto_alpha/bin_sc_rollup_node/inbox.ml +++ b/src/proto_alpha/bin_sc_rollup_node/inbox.ml @@ -31,15 +31,19 @@ open Alpha_context let lift promise = Lwt.map Environment.wrap_tzresult promise +let genesis_inbox node_ctxt = + let genesis_level = + Raw_level.to_int32 node_ctxt.Node_context.genesis_info.level + in + Plugin.RPC.Sc_rollup.inbox + node_ctxt.cctxt + (node_ctxt.cctxt#chain, `Level genesis_level) + module State = struct let add_messages = Store.Messages.add let add_inbox = Store.Inboxes.add - let add_history = Store.Histories.add - - let add_messages_history = Store.Payloads_histories.add - let level_of_hash = State.level_of_hash (** [inbox_of_head node_ctxt store block] returns the latest inbox at the @@ -48,7 +52,11 @@ module State = struct let inbox_of_head node_ctxt Layer1.{hash = block_hash; level = block_level} = let open Lwt_result_syntax in let open Node_context in - let*! possible_inbox = Store.Inboxes.find node_ctxt.store block_hash in + let*! possible_inbox = + let open Lwt_option_syntax in + let* l2_block = Store.L2_blocks.find node_ctxt.store block_hash in + Store.Inboxes.find node_ctxt.store l2_block.header.inbox_hash + in (* Pre-condition: forall l. (l > genesis_level) => inbox[l] <> None. *) match possible_inbox with | None -> @@ -58,8 +66,8 @@ module State = struct at the end of the origination level. *) let genesis_level = Raw_level.to_int32 node_ctxt.genesis_info.level in if block_level = genesis_level then - let Node_context.{cctxt; _} = node_ctxt in - Plugin.RPC.Sc_rollup.inbox cctxt (cctxt#chain, `Level genesis_level) + let+ inbox = genesis_inbox node_ctxt in + inbox else if block_level > genesis_level then (* Invariant broken, the inbox for this level should exist. *) failwith @@ -75,27 +83,6 @@ module State = struct the scope of the rollup's node" block_level | Some inbox -> return inbox - - let history_of_head node_ctxt Layer1.{hash = block_hash; level = block_level} - = - let open Lwt_result_syntax in - let open Node_context in - let*! res = Store.Histories.find node_ctxt.store block_hash in - match res with - | Some history -> return history - | None -> - (* We won't find inboxes for blocks before the rollup origination level. - Fortunately this case will only ever be called once when dealing with - the rollup origination block. After that we would always find an - inbox. *) - let genesis_level = Raw_level.to_int32 node_ctxt.genesis_info.level in - if block_level <= genesis_level then - return @@ Sc_rollup.Inbox.History.empty ~capacity:60000L - else - failwith - "The inbox history for hash %a is missing." - Tezos_crypto.Block_hash.pp - block_hash end let get_messages Node_context.{l1_ctxt; _} head = @@ -162,28 +149,30 @@ let same_inbox_as_layer_1 node_ctxt head_hash inbox = (Sc_rollup.Inbox.equal layer1_inbox inbox) (Sc_rollup_node_errors.Inconsistent_inbox {layer1_inbox; inbox}) -let add_messages ~predecessor_timestamp ~predecessor inbox history messages = +let add_messages ~predecessor_timestamp ~predecessor inbox messages = let open Lwt_result_syntax in + let no_history = Sc_rollup.Inbox.History.empty ~capacity:0L in lift @@ let*? ( messages_history, - history, + _no_history, inbox, witness, messages_with_protocol_internal_messages ) = Sc_rollup.Inbox.add_all_messages ~predecessor_timestamp ~predecessor - history + no_history inbox messages in let witness_hash = Sc_rollup.Inbox_merkelized_payload_hashes.hash witness in + let inbox_hash = Sc_rollup.Inbox.hash inbox in return ( messages_history, witness_hash, - history, + inbox_hash, inbox, messages_with_protocol_internal_messages ) @@ -205,7 +194,6 @@ let process_head (node_ctxt : _ Node_context.t) let* inbox = State.inbox_of_head node_ctxt predecessor in let inbox_metrics = Metrics.Inbox.metrics in Prometheus.Gauge.set inbox_metrics.head_inbox_level @@ Int32.to_float level ; - let* history = State.history_of_head node_ctxt predecessor in let*? level = Environment.wrap_tzresult @@ Raw_level.of_int32 level in let* ctxt = if Raw_level.(level <= node_ctxt.Node_context.genesis_info.level) then @@ -223,16 +211,15 @@ let process_head (node_ctxt : _ Node_context.t) (Raw_level.to_int32 level) (List.length collected_messages) in - let* ( messages_history, - messages_hash, - history, + let* ( _messages_history, + witness_hash, + inbox_hash, inbox, messages_with_protocol_internal_messages ) = add_messages ~predecessor_timestamp ~predecessor:predecessor_hash inbox - history collected_messages in Metrics.Inbox.Stats.head_messages_list := @@ -240,30 +227,59 @@ let process_head (node_ctxt : _ Node_context.t) let*! () = State.add_messages node_ctxt.store - head_hash - messages_with_protocol_internal_messages + witness_hash + { + predecessor = predecessor_hash; + predecessor_timestamp; + messages = collected_messages; + } in let* () = same_inbox_as_layer_1 node_ctxt head_hash inbox in - let*! () = - State.add_messages_history node_ctxt.store messages_hash messages_history - in - let*! () = State.add_inbox node_ctxt.store head_hash inbox in - let*! () = State.add_history node_ctxt.store head_hash history in - return ctxt) - else return (Context.empty node_ctxt.context) + let*! () = State.add_inbox node_ctxt.store inbox_hash inbox in + return + ( inbox_hash, + inbox, + witness_hash, + messages_with_protocol_internal_messages, + ctxt )) + else + let* inbox = genesis_inbox node_ctxt in + return + ( Sc_rollup.Inbox.hash inbox, + inbox, + Sc_rollup.Inbox.current_witness inbox, + [], + Context.empty node_ctxt.context ) let inbox_of_hash node_ctxt hash = let open Lwt_result_syntax in let* level = State.level_of_hash node_ctxt.Node_context.store hash in State.inbox_of_head node_ctxt {hash; level} -let history_of_hash node_ctxt hash = - let open Lwt_result_syntax in - let* level = State.level_of_hash node_ctxt.Node_context.store hash in - State.history_of_head node_ctxt {hash; level} - let inbox_of_head = State.inbox_of_head -let history_of_head = State.history_of_head - let start () = Inbox_event.starting () + +let payloads_history_of_messages ~predecessor ~predecessor_timestamp messages = + let open Result_syntax in + Environment.wrap_tzresult + @@ let* dummy_inbox = + (* The inbox is not necessary to compute the payloads *) + Sc_rollup.Inbox.genesis + ~predecessor_timestamp + ~predecessor + Raw_level.root + in + let+ ( payloads_history, + _history, + _inbox, + _witness, + _messages_with_protocol_internal_messages ) = + Sc_rollup.Inbox.add_all_messages + ~predecessor_timestamp + ~predecessor + (Sc_rollup.Inbox.History.empty ~capacity:0L) + dummy_inbox + messages + in + payloads_history diff --git a/src/proto_alpha/bin_sc_rollup_node/inbox.mli b/src/proto_alpha/bin_sc_rollup_node/inbox.mli index 6f3dbbebc5e68abcdcf31a5229b7f05539144439..4765aceb02d15b53ccc68c08dea340b3caff0eed 100644 --- a/src/proto_alpha/bin_sc_rollup_node/inbox.mli +++ b/src/proto_alpha/bin_sc_rollup_node/inbox.mli @@ -39,44 +39,52 @@ open Sc_rollup (** [process_head node_ctxt head operations] changes the state of the inbox to react to [head]. In particular, this process filters the provided [operations] of the [head] block. *) -val process_head : Node_context.rw -> Layer1.head -> Context.rw tzresult Lwt.t +val process_head : + Node_context.rw -> + Layer1.head -> + (Sc_rollup.Inbox.Hash.t + * Sc_rollup.Inbox.t + * Sc_rollup.Inbox_merkelized_payload_hashes.Hash.t + * Sc_rollup.Inbox_message.t list + * Context.rw) + tzresult + Lwt.t (** [inbox_of_hash node_ctxt block_hash] returns the rollup inbox at the end of - the given validation of [block_hash]. *) + the given validation of [block_hash]. NOTE: It requires the L2 block for + [block_hash] to have been saved. *) val inbox_of_hash : _ Node_context.t -> Tezos_crypto.Block_hash.t -> Inbox.t tzresult Lwt.t -(** [history_of_hash node_ctxt block_hash] returns the rollup inbox history at - the end of the given validation of [block_hash]. *) -val history_of_hash : - _ Node_context.t -> - Tezos_crypto.Block_hash.t -> - Inbox.History.t tzresult Lwt.t - (** [inbox_of_head node_ctxt block_head] returns the rollup inbox at the end of - the given validation of [block_head]. *) + the given validation of [block_head]. NOTE: It requires the L2 block for + [block_hash] to have been saved. *) val inbox_of_head : _ Node_context.t -> Layer1.head -> Inbox.t tzresult Lwt.t -(** [history_of_head node_ctxt block_head] returns the rollup inbox history at - the end of the given validation of [block_head]. *) -val history_of_head : - _ Node_context.t -> Layer1.head -> Inbox.History.t tzresult Lwt.t - (** [start ()] initializes the inbox to track the messages being published. *) val start : unit -> unit Lwt.t -(** [add_messages ~timestamp ~predecessor inbox history messages] adds - [messages] to the [inbox] using {Inbox.add_all_messages}. *) +(** [add_messages ~predecessor_timestamp ~predecessor inbox messages] adds + [messages] to the [inbox] using {!Inbox.add_all_messages}. *) val add_messages : predecessor_timestamp:Timestamp.time -> predecessor:Tezos_crypto.Block_hash.t -> Inbox.t -> - Inbox.History.t -> Inbox_message.t list -> (Inbox_merkelized_payload_hashes.History.t * Inbox_merkelized_payload_hashes.Hash.t - * Inbox.History.t + * Inbox.Hash.t * Inbox.t * Inbox_message.t list) tzresult Lwt.t + +(** [payloads_history_of_messages ~predecessor ~predecessor_timestamp messages] + builds the payloads history for the list of [messages]. This allows to not + store payloads histories (which contain merkelized skip lists) but simply + messages. *) +val payloads_history_of_messages : + predecessor:Tezos_crypto.Block_hash.t -> + predecessor_timestamp:Timestamp.time -> + Sc_rollup.Inbox_message.t list -> + Sc_rollup.Inbox_merkelized_payload_hashes.History.t tzresult diff --git a/src/proto_alpha/bin_sc_rollup_node/interpreter.ml b/src/proto_alpha/bin_sc_rollup_node/interpreter.ml index 711bf576cdddc3f7043e20a5235840b5817d2668..88918759e11afbe80d5e73714ed432c6ffb4389b 100644 --- a/src/proto_alpha/bin_sc_rollup_node/interpreter.ml +++ b/src/proto_alpha/bin_sc_rollup_node/interpreter.ml @@ -36,7 +36,11 @@ module type S = sig Fueled_pvm.S with module PVM = PVM and type fuel = Fuel.Free.t val process_head : - Node_context.rw -> Context.rw -> Layer1.head -> unit tzresult Lwt.t + Node_context.rw -> + 'a Context.t -> + Layer1.head -> + Sc_rollup.Inbox.t * Sc_rollup.Inbox_message.t list -> + ('a Context.t * int * int64 * Sc_rollup.Tick.t) tzresult Lwt.t val state_of_tick : _ Node_context.t -> @@ -126,7 +130,8 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct (** [transition_pvm node_ctxt predecessor head] runs a PVM at the previous state from block [predecessor] by consuming as many messages as possible from block [head]. *) - let transition_pvm node_ctxt ctxt predecessor Layer1.{hash; _} = + let transition_pvm node_ctxt ctxt predecessor Layer1.{hash = _; _} + inbox_messages = let open Lwt_result_syntax in (* Retrieve the previous PVM state from store. *) let* ctxt, predecessor_state = state_of_head node_ctxt ctxt predecessor in @@ -134,46 +139,22 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct Free_pvm.eval_block_inbox ~fuel:(Fuel.Free.of_ticks 0L) node_ctxt - hash + inbox_messages predecessor_state in let*! state, num_messages, inbox_level, _fuel = Delayed_write_monad.apply node_ctxt eval_result in - (* Write final state to store. *) let*! ctxt = PVM.State.set ctxt state in - let*! context_hash = Context.commit ctxt in - let*! () = Store.Contexts.add node_ctxt.store hash context_hash in - - (* Compute extra information about the state. *) let*! initial_tick = PVM.get_tick predecessor_state in - - let*! () = - let open Store.StateHistoryRepr in - let Layer1.{hash = predecessor_hash; _} = predecessor in - let event = - { - tick = initial_tick; - block_hash = hash; - predecessor_hash; - level = inbox_level; - } - in - Store.StateHistory.insert node_ctxt.store event - in - let*! last_tick = PVM.get_tick state in (* TODO: #2717 The number of ticks should not be an arbitrarily-sized integer or the difference between two ticks should be made an arbitrarily-sized integer too. *) - let num_ticks = Sc_rollup.Tick.distance initial_tick last_tick in - let*! () = - Store.StateInfo.add - node_ctxt.store - hash - {num_messages; num_ticks; initial_tick} + let num_ticks = + Sc_rollup.Tick.distance initial_tick last_tick |> Z.to_int64 in (* Produce events. *) let*! state_hash = PVM.state_hash state in @@ -184,11 +165,10 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct last_tick num_messages in - - return_unit + return (ctxt, num_messages, num_ticks, initial_tick) (** [process_head node_ctxt head] runs the PVM for the given head. *) - let process_head (node_ctxt : _ Node_context.t) ctxt head = + let process_head (node_ctxt : _ Node_context.t) ctxt head inbox_messages = let open Lwt_result_syntax in let first_inbox_level = Raw_level.to_int32 node_ctxt.genesis_info.level |> Int32.succ @@ -197,46 +177,48 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct let* predecessor = Layer1.get_predecessor node_ctxt.Node_context.l1_ctxt head in - transition_pvm node_ctxt ctxt predecessor head + transition_pvm node_ctxt ctxt predecessor head inbox_messages else if head.Layer1.level = Raw_level.to_int32 node_ctxt.genesis_info.level then let* ctxt, state = genesis_state head.hash node_ctxt ctxt in (* Write final state to store. *) let*! ctxt = PVM.State.set ctxt state in - let*! context_hash = Context.commit ctxt in - let*! () = Store.Contexts.add node_ctxt.store head.hash context_hash in - let*! () = - Store.StateInfo.add - node_ctxt.store - head.hash - { - num_messages = Z.zero; - num_ticks = Z.zero; - initial_tick = Sc_rollup.Tick.initial; - } - in - return_unit - else return_unit + (* let*! context_hash = Context.commit ctxt in *) + (* let*! () = Store.Contexts.add node_ctxt.store head.hash context_hash in *) + return (ctxt, 0, 0L, Sc_rollup.Tick.initial) + else return (ctxt, 0, 0L, Sc_rollup.Tick.initial) - (** [run_for_ticks node_ctxt predecessor_hash hash tick_distance] starts the - evaluation of the inbox at block [hash] for at most [tick_distance]. *) - let run_for_ticks node_ctxt predecessor_hash hash level tick_distance = + (** [run_for_ticks node_ctxt block tick_distance] starts the + evaluation of the inbox at [block] for at most [tick_distance]. *) + let run_for_ticks node_ctxt (block : Sc_rollup_block.t) tick_distance = let open Lwt_result_syntax in let open Delayed_write_monad.Lwt_result_syntax in - let pred_level = Raw_level.to_int32 level |> Int32.pred in - let* ctxt = Node_context.checkout_context node_ctxt predecessor_hash in + let pred_level = Raw_level.to_int32 block.header.level |> Int32.pred in + let* ctxt = + Node_context.checkout_context node_ctxt block.header.predecessor + in let* _ctxt, state = state_of_head node_ctxt ctxt - Layer1.{hash = predecessor_hash; level = pred_level} + Layer1.{hash = block.header.predecessor; level = pred_level} + in + let*! inbox = Store.Inboxes.get node_ctxt.store block.header.inbox_hash in + let*! {predecessor; predecessor_timestamp; messages} = + Store.Messages.get node_ctxt.store block.header.inbox_witness + in + let messages = + Sc_rollup.Inbox_message.Internal Start_of_level + :: Internal (Info_per_level {predecessor; predecessor_timestamp}) + :: messages + @ [Internal End_of_level] in let>* state, _counter, _level, _fuel = Accounted_pvm.eval_block_inbox ~fuel:(Fuel.Accounted.of_ticks tick_distance) node_ctxt - hash + (inbox, messages) state in return state @@ -246,18 +228,14 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct [None].*) let state_of_tick node_ctxt tick level = let open Lwt_result_syntax in - let* closest_event = - Store.StateHistory.event_of_largest_tick_before - node_ctxt.Node_context.store - tick - in - match closest_event with + let* closest_block = State.block_before node_ctxt.Node_context.store tick in + match closest_block with | None -> return None | Some event -> - if Raw_level.(event.level > level) then return None + if Raw_level.(event.header.level > level) then return None else let tick_distance = - Sc_rollup.Tick.distance tick event.tick |> Z.to_int64 + Sc_rollup.Tick.distance tick event.initial_tick |> Z.to_int64 in (* TODO: #3384 We assume that [StateHistory] correctly stores enough @@ -266,14 +244,7 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct [event.block_hash] is the block where the tick happened. We should test that this is always true because [state_of_tick] is a critical function. *) - let* state = - run_for_ticks - node_ctxt - event.predecessor_hash - event.block_hash - event.level - tick_distance - in + let* state = run_for_ticks node_ctxt event tick_distance in let state = Delayed_write_monad.ignore state in let*! hash = PVM.state_hash state in return (Some (state, hash)) diff --git a/src/proto_alpha/bin_sc_rollup_node/interpreter.mli b/src/proto_alpha/bin_sc_rollup_node/interpreter.mli index 9f3fbc27871ee964a84fb4f3e215294c62d7d988..18281abb76f28d17fec42853273ca1f28d9aaa93 100644 --- a/src/proto_alpha/bin_sc_rollup_node/interpreter.mli +++ b/src/proto_alpha/bin_sc_rollup_node/interpreter.mli @@ -34,11 +34,19 @@ module type S = sig module Free_pvm : Fueled_pvm.S with module PVM = PVM and type fuel = Fuel.Free.t - (** [process_head node_ctxt head] interprets the messages associated - with a [head] from a chain [event]. This requires the inbox to be updated - beforehand. *) + (** [process_head node_ctxt head (inbox, messages)] interprets the [messages] + associated with a [head]. This requires the [inbox] to be updated + beforehand. It returns [(ctxt, num_messages, num_ticks, tick)] where + [ctxt] is the updated layer 2 context (with the new PVM state), + [num_messages] is the number of [messages], [num_ticks] is the number of + ticks taken by the PVM for the evaluation and [tick] is the tick reached + by the PVM after the evaluation. *) val process_head : - Node_context.rw -> Context.rw -> Layer1.head -> unit tzresult Lwt.t + Node_context.rw -> + 'a Context.t -> + Layer1.head -> + Sc_rollup.Inbox.t * Sc_rollup.Inbox_message.t list -> + ('a Context.t * int * int64 * Sc_rollup.Tick.t) tzresult Lwt.t (** [state_of_tick node_ctxt tick level] returns [Some (state, hash)] for a given [tick] if this [tick] happened before diff --git a/src/proto_alpha/bin_sc_rollup_node/interpreter_event.ml b/src/proto_alpha/bin_sc_rollup_node/interpreter_event.ml index 49152eed397aa73e17c7549827d6696fa223717d..ffc016b9123b695c0084c840b202617a92dede29 100644 --- a/src/proto_alpha/bin_sc_rollup_node/interpreter_event.ml +++ b/src/proto_alpha/bin_sc_rollup_node/interpreter_event.ml @@ -41,7 +41,7 @@ module Simple = struct ("inbox_level", Protocol.Alpha_context.Raw_level.encoding) ("state_hash", State_hash.encoding) ("ticks", Tick.encoding) - ("num_messages", Data_encoding.z) + ("num_messages", Data_encoding.int31) let intended_failure = declare_4 diff --git a/src/proto_alpha/bin_sc_rollup_node/node_context.ml b/src/proto_alpha/bin_sc_rollup_node/node_context.ml index f4d20c3970da48ba47043273bd194c824cfbed0b..d05a41ceac5549802df864f44a4e67940cc2fd1a 100644 --- a/src/proto_alpha/bin_sc_rollup_node/node_context.ml +++ b/src/proto_alpha/bin_sc_rollup_node/node_context.ml @@ -143,19 +143,19 @@ let init (cctxt : Protocol_client_context.full) dal_cctxt ~data_dir l1_ctxt let checkout_context node_ctxt block_hash = let open Lwt_result_syntax in - let*! context_hash = Store.Contexts.find node_ctxt.store block_hash in + let*! l2_block = Store.L2_blocks.find node_ctxt.store block_hash in let*? context_hash = - match context_hash with + match l2_block with | None -> error (Sc_rollup_node_errors.Cannot_checkout_context (block_hash, None)) - | Some context_hash -> ok context_hash + | Some {header = {context; _}; _} -> ok context in let*! ctxt = Context.checkout node_ctxt.context context_hash in match ctxt with | None -> tzfail (Sc_rollup_node_errors.Cannot_checkout_context - (block_hash, Some (Context.hash_to_raw_string context_hash))) + (block_hash, Some context_hash)) | Some ctxt -> return ctxt let metadata node_ctxt = @@ -174,3 +174,13 @@ let readonly (node_ctxt : _ t) = } type 'a delayed_write = ('a, rw) Delayed_write_monad.t + +let get_full_l2_block {store; _} block_hash = + let open Lwt_syntax in + let* block = Store.L2_blocks.get store block_hash in + let* inbox = Store.Inboxes.get store block.header.inbox_hash + and* {messages; _} = Store.Messages.get store block.header.inbox_witness + and* commitment = + Option.map_s (Store.Commitments.get store) block.header.commitment_hash + in + return {block with content = {Sc_rollup_block.inbox; messages; commitment}} diff --git a/src/proto_alpha/bin_sc_rollup_node/node_context.mli b/src/proto_alpha/bin_sc_rollup_node/node_context.mli index db431e7a77fd6b23f25b851a8f3d97fdf7686cef..3611b0e41cca26bacd35af34e8be9fa6c7758a65 100644 --- a/src/proto_alpha/bin_sc_rollup_node/node_context.mli +++ b/src/proto_alpha/bin_sc_rollup_node/node_context.mli @@ -127,3 +127,9 @@ val readonly : _ t -> ro (** Monad for values with delayed write effects in the node context. *) type 'a delayed_write = ('a, rw) Delayed_write_monad.t + +(** [get_full_l2_block node_ctxt hash] returns the full L2 block for L1 block + hash [hash]. The result contains the L2 block and its content (inbox, + messages, commitment). *) +val get_full_l2_block : + _ t -> Tezos_crypto.Block_hash.t -> Sc_rollup_block.full Lwt.t diff --git a/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml b/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml index 39811449f42282b59c4a21743683617e9cc7adcf..b215f13c6ec8f46cbf705301713faf405d26db70 100644 --- a/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml +++ b/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml @@ -171,8 +171,7 @@ module Make (Interpreter : Interpreter.S) : let snapshot_head = Layer1.{hash = snapshot_hash; level = snapshot_level_int32} in - let* snapshot_inbox = Inbox.inbox_of_hash node_ctxt snapshot_hash in - let* snapshot_history = Inbox.history_of_hash node_ctxt snapshot_hash in + let* snapshot_inbox = Inbox.inbox_of_head node_ctxt snapshot_head in let* snapshot_ctxt = Node_context.checkout_context node_ctxt snapshot_hash in @@ -225,10 +224,20 @@ module Make (Interpreter : Interpreter.S) : let inbox = snapshot let get_history inbox_hash = - Sc_rollup.Inbox.History.find inbox_hash snapshot_history |> Lwt.return - - let get_payloads_history = - Store.Payloads_histories.get node_ctxt.Node_context.store + let open Lwt_option_syntax in + let+ inbox = Store.Inboxes.find node_ctxt.store inbox_hash in + Sc_rollup.Inbox.take_snapshot inbox + + let get_payloads_history witness = + let open Lwt_syntax in + let+ {predecessor; predecessor_timestamp; messages} = + Store.Messages.get node_ctxt.store witness + in + Inbox.payloads_history_of_messages + ~predecessor + ~predecessor_timestamp + messages + |> WithExceptions.Result.get_ok ~loc:__LOC__ end module Dal_with_history = struct @@ -249,7 +258,7 @@ module Make (Interpreter : Interpreter.S) : let* proof = trace (Sc_rollup_node_errors.Cannot_produce_proof - (snapshot_inbox, snapshot_history, game.inbox_level)) + (snapshot_inbox, game.inbox_level)) @@ (Sc_rollup.Proof.produce ~metadata (module P) game.inbox_level >|= Environment.wrap_tzresult) in diff --git a/src/proto_alpha/bin_sc_rollup_node/sc_rollup_node_errors.ml b/src/proto_alpha/bin_sc_rollup_node/sc_rollup_node_errors.ml index 1328c3c30f94afa155ce4cc7a77b18784898f883..ceabd95165019890c2df93aa02a6a9762dc55f07 100644 --- a/src/proto_alpha/bin_sc_rollup_node/sc_rollup_node_errors.ml +++ b/src/proto_alpha/bin_sc_rollup_node/sc_rollup_node_errors.ml @@ -28,8 +28,7 @@ open Protocol.Alpha_context let tez_sym = "\xEA\x9C\xA9" type error += - | Cannot_produce_proof of - Sc_rollup.Inbox.t * Sc_rollup.Inbox.History.t * Raw_level.t + | Cannot_produce_proof of Sc_rollup.Inbox.t * Raw_level.t | Missing_mode_operators of {mode : string; missing_operators : string list} | Bad_minimal_fees of string | Commitment_predecessor_should_be_LCC of Sc_rollup.Commitment.t @@ -39,7 +38,8 @@ type error += inbox : Sc_rollup.Inbox.t; } | Missing_PVM_state of Tezos_crypto.Block_hash.t * Int32.t - | Cannot_checkout_context of Tezos_crypto.Block_hash.t * string option + | Cannot_checkout_context of + Tezos_crypto.Block_hash.t * Sc_rollup_context_hash.t option | No_batcher type error += @@ -99,27 +99,21 @@ let () = ~description: "The rollup node is in a state that prevents it from producing \ refutation proofs." - ~pp:(fun ppf (inbox, history, level) -> + ~pp:(fun ppf (inbox, level) -> Format.fprintf ppf - "cannot produce proof for inbox %a of level %a with history %a" + "cannot produce proof for inbox %a of level %a" Sc_rollup.Inbox.pp inbox Raw_level.pp - level - Sc_rollup.Inbox.History.pp - history) + level) Data_encoding.( - obj3 + obj2 (req "inbox" Sc_rollup.Inbox.encoding) - (req "history" Sc_rollup.Inbox.History.encoding) (req "level" Raw_level.encoding)) (function - | Cannot_produce_proof (inbox, history, level) -> - Some (inbox, history, level) - | _ -> None) - (fun (inbox, history, level) -> - Cannot_produce_proof (inbox, history, level)) ; + | Cannot_produce_proof (inbox, level) -> Some (inbox, level) | _ -> None) + (fun (inbox, level) -> Cannot_produce_proof (inbox, level)) ; register_error_kind ~id:"sc_rollup.node.missing_mode_operators" @@ -197,14 +191,14 @@ let () = "The context %sfor block %a cannot be checkouted" (Option.fold ~none:"" - ~some:(fun c -> Hex.(show (of_string c))) + ~some:Sc_rollup_context_hash.to_b58check context_hash) Tezos_crypto.Block_hash.pp block) Data_encoding.( obj2 (req "block" Tezos_crypto.Block_hash.encoding) - (opt "context" (conv Bytes.of_string Bytes.to_string bytes))) + (opt "context" Sc_rollup_context_hash.encoding)) (function | Cannot_checkout_context (block, context) -> Some (block, context) | _ -> None) diff --git a/src/proto_alpha/bin_sc_rollup_node/state.ml b/src/proto_alpha/bin_sc_rollup_node/state.ml index ff76af338d9c0e7151d8b8887c193f588bfb98d6..7d916f43ec266c6a4e19f8b2133022530bf657bd 100644 --- a/src/proto_alpha/bin_sc_rollup_node/state.ml +++ b/src/proto_alpha/bin_sc_rollup_node/state.ml @@ -22,41 +22,28 @@ (* DEALINGS IN THE SOFTWARE. *) (* *) (*****************************************************************************) +open Protocol +open Alpha_context +module Raw_store = Store module Store = struct - (** Table from blocks hashes to unit. The entry is present iff the block - identified by that hash is fully processed by the rollup node. *) - module Processed_hashes = - Store.Make_append_only_map - (struct - let path = ["tezos"; "processed_blocks"] - end) - (struct - type key = Tezos_crypto.Block_hash.t - - let to_path_representation = Tezos_crypto.Block_hash.to_b58check - end) - (struct - type value = unit - - let name = "processed" - - let encoding = Data_encoding.unit - end) - - module Last_processed_head = + (* TODO: https://gitlab.com/tezos/tezos/-/issues/4392 + Use file. *) + module L2_head = Store.Make_mutable_value (struct - let path = ["tezos"; "processed_head"] + let path = ["l2_head"] end) (struct - type value = Layer1.head + type value = Sc_rollup_block.t - let name = "head" + let name = "l2_block" - let encoding = Layer1.head_encoding + let encoding = Sc_rollup_block.encoding end) + (* TODO: https://gitlab.com/tezos/tezos/-/issues/4392 + Use file. *) module Last_finalized_head = Store.Make_mutable_value (struct @@ -119,14 +106,14 @@ let level_of_hash store hash = failwith "No level known for block %a" Tezos_crypto.Block_hash.pp hash | Some l -> return l -let mark_processed_head store Layer1.({hash; level = _} as head) = +let save_l2_block store (head : Sc_rollup_block.t) = let open Lwt_syntax in - let* () = Store.Processed_hashes.add store hash () in - Store.Last_processed_head.set store head + let* () = Raw_store.L2_blocks.add store head.header.block_hash head in + Store.L2_head.set store head -let is_processed store head = Store.Processed_hashes.mem store head +let is_processed store head = Raw_store.L2_blocks.mem store head -let last_processed_head_opt store = Store.Last_processed_head.find store +let last_processed_head_opt store = Store.L2_head.find store let mark_finalized_head store head = Store.Last_finalized_head.set store head @@ -136,3 +123,24 @@ let set_block_level_and_hash store Layer1.{hash; level} = let open Lwt_syntax in let* () = Store.Hashes_to_levels.add store hash level in Store.Levels_to_hashes.add store level hash + +(* TODO: https://gitlab.com/tezos/tezos/-/issues/4532 + Make this logarithmic, by storing pointers to muliple predecessor and + by dichotomy. *) +let block_before store tick = + let open Lwt_result_syntax in + let*! head = Store.L2_head.find store in + match head with + | None -> return_none + | Some head -> + let rec search block_hash = + let*! block = Raw_store.L2_blocks.find store block_hash in + match block with + | None -> + failwith "Missing block %a" Tezos_crypto.Block_hash.pp block_hash + | Some block -> + if Sc_rollup.Tick.(block.initial_tick <= tick) then + return_some block + else search block.header.predecessor + in + search head.header.block_hash diff --git a/src/proto_alpha/bin_sc_rollup_node/state.mli b/src/proto_alpha/bin_sc_rollup_node/state.mli index 0b6e426bf6bd9f593859b00eebfdccc245fe153b..9db757186c6d894cdb91d642ef20637daef2cbea 100644 --- a/src/proto_alpha/bin_sc_rollup_node/state.mli +++ b/src/proto_alpha/bin_sc_rollup_node/state.mli @@ -23,17 +23,19 @@ (* *) (*****************************************************************************) +open Protocol.Alpha_context + (** [is_processed store hash] returns [true] if the block with [hash] has already been processed by the daemon. *) val is_processed : _ Store.t -> Tezos_crypto.Block_hash.t -> bool Lwt.t (** [mark_processed_head store head] remembers that the [head] is processed. The system should not have to come back to it. *) -val mark_processed_head : Store.rw -> Layer1.head -> unit Lwt.t +val save_l2_block : Store.rw -> Sc_rollup_block.t -> unit Lwt.t (** [last_processed_head_opt store] returns the last processed head if it exists. *) -val last_processed_head_opt : _ Store.t -> Layer1.head option Lwt.t +val last_processed_head_opt : _ Store.t -> Sc_rollup_block.t option Lwt.t (** [mark_finalized_head store head] remembers that the [head] is finalized. By construction, every block whose level is smaller than [head]'s is also @@ -56,3 +58,10 @@ val level_of_hash : (** [set_block_level_and_hash store head] registers the correspondences [head.level |-> head.hash] and [head.hash |-> head.level] in the store. *) val set_block_level_and_hash : Store.rw -> Layer1.head -> unit Lwt.t + +(** [block_before store tick] returns the last layer 2 block whose initial tick + is before [tick]. *) +val block_before : + [> `Read] Store.store -> + Sc_rollup.Tick.t -> + Sc_rollup_block.t option tzresult Lwt.t diff --git a/src/proto_alpha/bin_sc_rollup_node/store.ml b/src/proto_alpha/bin_sc_rollup_node/store.ml index 4fd308e17a66a95281267ad8be3564d1c96e2553..3889830f6dd263a050cd0ef2ae2cb6996a370f13 100644 --- a/src/proto_alpha/bin_sc_rollup_node/store.ml +++ b/src/proto_alpha/bin_sc_rollup_node/store.ml @@ -50,14 +50,8 @@ let load = IStore.load let readonly = IStore.readonly -type state_info = { - num_messages : Z.t; - num_ticks : Z.t; - initial_tick : Sc_rollup.Tick.t; -} - -(** Extraneous state information for the PVM *) -module StateInfo = +(** L2 blocks *) +module L2_blocks = Make_append_only_map (struct let path = ["state_info"] @@ -68,110 +62,54 @@ module StateInfo = let to_path_representation = Tezos_crypto.Block_hash.to_b58check end) (struct - type value = state_info + type value = Sc_rollup_block.t - let name = "state_info" + let name = "sc_rollup_block" - let encoding = - let open Data_encoding in - conv - (fun {num_messages; num_ticks; initial_tick} -> - (num_messages, num_ticks, initial_tick)) - (fun (num_messages, num_ticks, initial_tick) -> - {num_messages; num_ticks; initial_tick}) - (obj3 - (req "num_messages" Data_encoding.z) - (req "num_ticks" Data_encoding.z) - (req "initial_tick" Sc_rollup.Tick.encoding)) + let encoding = Sc_rollup_block.encoding end) -module StateHistoryRepr = struct - type event = { - tick : Sc_rollup.Tick.t; - block_hash : Tezos_crypto.Block_hash.t; - predecessor_hash : Tezos_crypto.Block_hash.t; - level : Raw_level.t; +(** Unaggregated messages per block *) +module Messages = struct + type info = { + predecessor : Tezos_crypto.Block_hash.t; + predecessor_timestamp : Timestamp.t; + messages : Sc_rollup.Inbox_message.t list; } - module TickMap = Map.Make (Sc_rollup.Tick) - - type value = event TickMap.t - - let event_encoding = - let open Data_encoding in - conv - (fun {tick; block_hash; predecessor_hash; level} -> - (tick, block_hash, predecessor_hash, level)) - (fun (tick, block_hash, predecessor_hash, level) -> - {tick; block_hash; predecessor_hash; level}) - (obj4 - (req "tick" Sc_rollup.Tick.encoding) - (req "block_hash" Tezos_crypto.Block_hash.encoding) - (req "predecessor_hash" Tezos_crypto.Block_hash.encoding) - (req "level" Raw_level.encoding)) - - let name = "state_history" - let encoding = let open Data_encoding in conv - TickMap.bindings - (fun bindings -> TickMap.of_seq (List.to_seq bindings)) - (Data_encoding.list (tup2 Sc_rollup.Tick.encoding event_encoding)) -end + (fun {predecessor; predecessor_timestamp; messages} -> + (predecessor, predecessor_timestamp, messages)) + (fun (predecessor, predecessor_timestamp, messages) -> + {predecessor; predecessor_timestamp; messages}) + @@ obj3 + (req "predecessor" Tezos_crypto.Block_hash.encoding) + (req "predecessor_timestamp" Timestamp.encoding) + (req + "messages" + (list @@ dynamic_size Sc_rollup.Inbox_message.encoding)) -module StateHistory = struct include - Make_mutable_value + Make_append_only_map (struct - let path = ["state_history"] + let path = ["messages"] end) - (StateHistoryRepr) - - let insert store event = - let open Lwt_result_syntax in - let open StateHistoryRepr in - let*! history = find store in - let history = - match history with - | None -> StateHistoryRepr.TickMap.empty - | Some history -> history - in - set store (TickMap.add event.tick event history) - - let event_of_largest_tick_before store tick = - let open Lwt_result_syntax in - let open StateHistoryRepr in - let*! history = find store in - match history with - | None -> return_none - | Some history -> ( - let events_before, opt_value, _ = TickMap.split tick history in - match opt_value with - | Some event -> return (Some event) - | None -> - return @@ Option.map snd @@ TickMap.max_binding_opt events_before) -end - -(** Unaggregated messages per block *) -module Messages = - Make_append_only_map - (struct - let path = ["messages"] - end) - (struct - type key = Tezos_crypto.Block_hash.t + (struct + type key = Sc_rollup.Inbox_merkelized_payload_hashes.Hash.t - let to_path_representation = Tezos_crypto.Block_hash.to_b58check - end) - (struct - type value = Sc_rollup.Inbox_message.t list + let to_path_representation = + Sc_rollup.Inbox_merkelized_payload_hashes.Hash.to_b58check + end) + (struct + type value = info - let name = "messages" + let name = "messages" - let encoding = - Data_encoding.(list @@ dynamic_size Sc_rollup.Inbox_message.encoding) - end) + let encoding = encoding + end) +end (** Inbox state for each block *) module Inboxes = @@ -180,9 +118,9 @@ module Inboxes = let path = ["inboxes"] end) (struct - type key = Tezos_crypto.Block_hash.t + type key = Sc_rollup.Inbox.Hash.t - let to_path_representation = Tezos_crypto.Block_hash.to_b58check + let to_path_representation = Sc_rollup.Inbox.Hash.to_b58check end) (struct type value = Sc_rollup.Inbox.t @@ -192,67 +130,26 @@ module Inboxes = let encoding = Sc_rollup.Inbox.encoding end) -(** Message history for the inbox at a given block *) -module Histories = - Make_append_only_map - (struct - let path = ["histories"] - end) - (struct - type key = Tezos_crypto.Block_hash.t - - let to_path_representation = Tezos_crypto.Block_hash.to_b58check - end) - (struct - type value = Sc_rollup.Inbox.History.t - - let name = "inbox_history" - - let encoding = Sc_rollup.Inbox.History.encoding - end) - -(** payloads history for the inbox at a given block *) -module Payloads_histories = - Make_append_only_map - (struct - let path = ["payloads_histories"] - end) - (struct - type key = Sc_rollup.Inbox_merkelized_payload_hashes.Hash.t - - let to_path_representation = - Sc_rollup.Inbox_merkelized_payload_hashes.Hash.to_b58check - end) - (struct - let name = "payloads_history" - - type value = Sc_rollup.Inbox_merkelized_payload_hashes.History.t - - let encoding = Sc_rollup.Inbox_merkelized_payload_hashes.History.encoding - end) - module Commitments = Make_append_only_map (struct let path = ["commitments"; "computed"] end) (struct - type key = Raw_level.t + type key = Sc_rollup.Commitment.Hash.t - let to_path_representation key = Int32.to_string @@ Raw_level.to_int32 key + let to_path_representation = Sc_rollup.Commitment.Hash.to_b58check end) (struct - type value = Sc_rollup.Commitment.t * Sc_rollup.Commitment.Hash.t + type value = Sc_rollup.Commitment.t - let name = "commitment_with_hash" + let name = "commitment" - let encoding = - Data_encoding.( - obj2 - (req "commitment" Sc_rollup.Commitment.encoding) - (req "hash" Sc_rollup.Commitment.Hash.encoding)) + let encoding = Sc_rollup.Commitment.encoding end) +(* TODO: https://gitlab.com/tezos/tezos/-/issues/4392 + Use file. *) module Last_stored_commitment_level = Make_mutable_value (struct @@ -284,24 +181,6 @@ module Commitments_published_at_level = let encoding = Raw_level.encoding end) -module Contexts = - Make_append_only_map - (struct - let path = ["contexts"] - end) - (struct - type key = Tezos_crypto.Block_hash.t - - let to_path_representation = Tezos_crypto.Block_hash.to_b58check - end) - (struct - type value = Context.hash - - let name = "context" - - let encoding = Context.hash_encoding - end) - (* Published slot headers per block hash, stored as a list of bindings from `Dal_slot_index.t` to `Dal.Slot.t`. The encoding function converts this @@ -437,15 +316,17 @@ module Dal_confirmed_slots_history = (** Confirmed DAL slots histories cache. See documentation of {Dal_slot_repr.Slots_history} for more details. *) module Dal_confirmed_slots_histories = - Make_append_only_map - (struct - let path = ["dal"; "confirmed_slots_histories_cache"] - end) - (struct - type key = Tezos_crypto.Block_hash.t + (* TODO: https://gitlab.com/tezos/tezos/-/issues/4390 + Store single history points in map instead of whole history. *) + Make_append_only_map + (struct + let path = ["dal"; "confirmed_slots_histories_cache"] + end) + (struct + type key = Tezos_crypto.Block_hash.t - let to_path_representation = Tezos_crypto.Block_hash.to_b58check - end) + let to_path_representation = Tezos_crypto.Block_hash.to_b58check + end) (struct type value = Dal.Slots_history.History_cache.t diff --git a/src/proto_alpha/bin_sc_rollup_node/store.mli b/src/proto_alpha/bin_sc_rollup_node/store.mli index 7fd56116bf0d9edbfb453dabbda1ac0a5fd9a390..c30861a40e78f86ff6558fc773af6516ae6ec816 100644 --- a/src/proto_alpha/bin_sc_rollup_node/store.mli +++ b/src/proto_alpha/bin_sc_rollup_node/store.mli @@ -47,12 +47,6 @@ type rw = Store_sigs.rw t (** Read only store {!t}. *) type ro = Store_sigs.ro t -type state_info = { - num_messages : Z.t; - num_ticks : Z.t; - initial_tick : Sc_rollup.Tick.t; -} - (** [close store] closes the store. *) val close : _ t -> unit Lwt.t @@ -62,81 +56,40 @@ val load : 'a Store_sigs.mode -> string -> 'a store Lwt.t (** [readonly store] returns a read-only version of [store]. *) val readonly : _ t -> ro -(** Extraneous state information for the PVM *) -module StateInfo : +module L2_blocks : Store_sigs.Append_only_map with type key := Tezos_crypto.Block_hash.t - and type value := state_info + and type value := Sc_rollup_block.t and type 'a store := 'a store -module StateHistoryRepr : sig - type event = { - tick : Sc_rollup.Tick.t; - block_hash : Tezos_crypto.Block_hash.t; - predecessor_hash : Tezos_crypto.Block_hash.t; - level : Raw_level.t; +(** Storage for persisting messages downloaded from the L1 node. *) +module Messages : sig + type info = { + predecessor : Tezos_crypto.Block_hash.t; + predecessor_timestamp : Timestamp.t; + messages : Sc_rollup.Inbox_message.t list; } - module TickMap : Map.S with type key := Sc_rollup.Tick.t - - type value = event TickMap.t -end - -(** [StateHistory] represents storage for the PVM state history: it is an - extension of [Store_utils.Mutable_value] whose values are lists of bindings - indexed by PVM tick numbers, and whose value contains information about the - block that the PVM was processing when generating the tick. -*) -module StateHistory : sig include - Store_sigs.Mutable_value - with type value := StateHistoryRepr.value + Store_sigs.Append_only_map + with type key := Sc_rollup.Inbox_merkelized_payload_hashes.Hash.t + and type value := info and type 'a store := 'a store - - val insert : rw -> StateHistoryRepr.event -> unit Lwt.t - - val event_of_largest_tick_before : - _ t -> Sc_rollup.Tick.t -> StateHistoryRepr.event option tzresult Lwt.t end -(** Storage for persisting messages downloaded from the L1 node, indexed by - [Tezos_crypto.Block_hash.t]. *) -module Messages : - Store_sigs.Append_only_map - with type key := Tezos_crypto.Block_hash.t - and type value := Sc_rollup.Inbox_message.t list - and type 'a store := 'a store - (** Aggregated collection of messages from the L1 inbox *) module Inboxes : Store_sigs.Append_only_map - with type key := Tezos_crypto.Block_hash.t + with type key := Sc_rollup.Inbox.Hash.t and type value := Sc_rollup.Inbox.t and type 'a store := 'a store -(** Histories from the rollup node. **) -module Histories : - Store_sigs.Append_only_map - with type key := Tezos_crypto.Block_hash.t - and type value := Sc_rollup.Inbox.History.t - and type 'a store := 'a store - -(** messages histories from the rollup node. Each history contains the messages - of one level. The store is indexed by a level in order to maintain a small - structure in memory. Only the message history of one level is fetched when - computing the proof. *) -module Payloads_histories : - Store_sigs.Append_only_map - with type key := Sc_rollup.Inbox_merkelized_payload_hashes.Hash.t - and type value := Sc_rollup.Inbox_merkelized_payload_hashes.History.t - and type 'a store := 'a store - (** Storage containing commitments and corresponding commitment hashes that the rollup node has knowledge of. *) module Commitments : Store_sigs.Append_only_map - with type key := Raw_level.t - and type value := Sc_rollup.Commitment.t * Sc_rollup.Commitment.Hash.t + with type key := Sc_rollup.Commitment.Hash.t + and type value := Sc_rollup.Commitment.t and type 'a store := 'a store (** Storage containing the inbox level of the last commitment produced by the @@ -155,13 +108,6 @@ module Commitments_published_at_level : and type value := Raw_level.t and type 'a store := 'a store -(** Storage containing the hashes of contexts retrieved from the L1 node. *) -module Contexts : - Store_sigs.Append_only_map - with type key := Tezos_crypto.Block_hash.t - and type value := Context.hash - and type 'a store := 'a store - (** Published slot headers per block hash, stored as a list of bindings from [Dal_slot_index.t] to [Dal.Slot.t]. The encoding function converts this diff --git a/src/proto_alpha/lib_sc_rollup/sc_rollup_block.ml b/src/proto_alpha/lib_sc_rollup/sc_rollup_block.ml new file mode 100644 index 0000000000000000000000000000000000000000..4715b8408387e3a070212960b7742dab106cde56 --- /dev/null +++ b/src/proto_alpha/lib_sc_rollup/sc_rollup_block.ml @@ -0,0 +1,202 @@ +(*****************************************************************************) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +open Protocol +open Alpha_context + +type header = { + block_hash : Tezos_crypto.Block_hash.t; + level : Raw_level.t; + predecessor : Tezos_crypto.Block_hash.t; + commitment_hash : Sc_rollup.Commitment.Hash.t option; + previous_commitment_hash : Sc_rollup.Commitment.Hash.t; + context : Sc_rollup_context_hash.t; + inbox_witness : Sc_rollup.Inbox_merkelized_payload_hashes.Hash.t; + inbox_hash : Sc_rollup.Inbox.Hash.t; +} + +type content = { + inbox : Sc_rollup.Inbox.t; + messages : Sc_rollup.Inbox_message.t list; + commitment : Sc_rollup.Commitment.t option; +} + +type ('header, 'content) block = { + header : 'header; + content : 'content; + initial_tick : Sc_rollup.Tick.t; + num_ticks : int64; +} + +type t = (header, unit) block + +type full = (header, content) block + +let commitment_hash_opt_encoding = + let open Data_encoding in + let binary = + conv + (Option.value ~default:Sc_rollup.Commitment.Hash.zero) + (fun h -> if Sc_rollup.Commitment.Hash.(h = zero) then None else Some h) + Sc_rollup.Commitment.Hash.encoding + in + let json = option Sc_rollup.Commitment.Hash.encoding in + splitted ~binary ~json + +let header_encoding = + let open Data_encoding in + conv + (fun { + block_hash; + level; + predecessor; + commitment_hash; + previous_commitment_hash; + context; + inbox_witness; + inbox_hash; + } -> + ( block_hash, + level, + predecessor, + commitment_hash, + previous_commitment_hash, + context, + inbox_witness, + inbox_hash )) + (fun ( block_hash, + level, + predecessor, + commitment_hash, + previous_commitment_hash, + context, + inbox_witness, + inbox_hash ) -> + { + block_hash; + level; + predecessor; + commitment_hash; + previous_commitment_hash; + context; + inbox_witness; + inbox_hash; + }) + @@ obj8 + (req + "block_hash" + Tezos_crypto.Block_hash.encoding + ~description:"Tezos block hash.") + (req + "level" + Raw_level.encoding + ~description: + "Level of the block, corresponds to the level of the tezos block.") + (req + "predecessor" + Tezos_crypto.Block_hash.encoding + ~description:"Predecessor hash of the Tezos block.") + (req + "commitment_hash" + commitment_hash_opt_encoding + ~description: + "Hash of this block's commitment if any was computed for it.") + (req + "previous_commitment_hash" + Sc_rollup.Commitment.Hash.encoding + ~description: + "Previous commitment hash in the chain. If there is a commitment \ + for this block, this field contains the commitment that was \ + previously computed.") + (req + "context" + Sc_rollup_context_hash.encoding + ~description:"Hash of the layer 2 context for this block.") + (req + "inbox_witness" + Sc_rollup.Inbox_merkelized_payload_hashes.Hash.encoding + ~description: + "Witness for the inbox for this block, i.e. the Merkle hash of \ + payloads of messages.") + (req + "inbox_hash" + Sc_rollup.Inbox.Hash.encoding + ~description:"Hash of the inbox for this block.") + +let header_size = + WithExceptions.Option.get ~loc:__LOC__ + @@ Data_encoding.Binary.fixed_length header_encoding + +let content_encoding = + let open Data_encoding in + conv + (fun {inbox; messages; commitment} -> (inbox, messages, commitment)) + (fun (inbox, messages, commitment) -> {inbox; messages; commitment}) + @@ obj3 + (req + "inbox" + Sc_rollup.Inbox.encoding + ~description:"Inbox for this block.") + (req + "messages" + (list (dynamic_size Sc_rollup.Inbox_message.encoding)) + ~description:"Messages added to the inbox in this block.") + (opt + "commitment" + Sc_rollup.Commitment.encoding + ~description:"Commitment, if any is computed for this block.") + +let block_encoding header_encoding content_encoding = + let open Data_encoding in + conv + (fun {header; content; initial_tick; num_ticks} -> + (header, (content, (initial_tick, num_ticks)))) + (fun (header, (content, (initial_tick, num_ticks))) -> + {header; content; initial_tick; num_ticks}) + @@ merge_objs header_encoding + @@ merge_objs content_encoding + @@ obj2 + (req + "initial_tick" + Sc_rollup.Tick.encoding + ~description: + "Initial tick of the PVM at this block, i.e. before evaluation of \ + the messages.") + (req + "num_ticks" + int64 + ~description: + "Number of ticks produced by the evaluation of the messages in \ + this block.") + +let encoding = block_encoding header_encoding Data_encoding.unit + +let full_encoding = block_encoding header_encoding content_encoding + +let most_recent_commitment (header : header) = + Option.value header.commitment_hash ~default:header.previous_commitment_hash + +let final_tick {initial_tick; num_ticks; _} = + Sc_rollup.Tick.jump initial_tick (Z.of_int64 num_ticks) diff --git a/src/proto_alpha/lib_sc_rollup/sc_rollup_block.mli b/src/proto_alpha/lib_sc_rollup/sc_rollup_block.mli new file mode 100644 index 0000000000000000000000000000000000000000..7d79a246d3e61ceb95984026ca913a3cc86e1dcd --- /dev/null +++ b/src/proto_alpha/lib_sc_rollup/sc_rollup_block.mli @@ -0,0 +1,112 @@ +(*****************************************************************************) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +open Protocol +open Alpha_context + +(** {2 Structure of layer 2 blocks} *) + +(** A layer 2 block header contains information about the inbox and commitment + with respect to a layer 1 block, but without the inbox content of + messages. *) +type header = { + block_hash : Tezos_crypto.Block_hash.t; (** Tezos block hash. *) + level : Raw_level.t; + (** Level of the block, corresponds to the level of the tezos block. *) + predecessor : Tezos_crypto.Block_hash.t; + (** Predecessor hash of the Tezos block. *) + commitment_hash : Sc_rollup.Commitment.Hash.t option; + (** Hash of this block's commitment if any was computed for it. *) + previous_commitment_hash : Sc_rollup.Commitment.Hash.t; + (** Previous commitment hash in the chain. If there is a commitment for this + block, this field contains the commitment that was previously + computed. *) + context : Sc_rollup_context_hash.t; + (** Hash of the layer 2 context for this block. *) + inbox_witness : Sc_rollup.Inbox_merkelized_payload_hashes.Hash.t; + (** Witness for the inbox for this block, i.e. the Merkle hash of payloads + of messages. *) + inbox_hash : Sc_rollup.Inbox.Hash.t; (** Hash of the inbox for this block. *) +} + +(** Contents of blocks which include the actual content of the inbox and + messages. *) +type content = { + inbox : Sc_rollup.Inbox.t; (** Inbox for this block. *) + messages : Sc_rollup.Inbox_message.t list; + (** Messages added to the inbox in this block. *) + commitment : Sc_rollup.Commitment.t option; + (** Commitment, if any is computed for this block. [header.commitment = + Some h] iff [commitment = Some c] and [hash c = h]. *) +} + +(** Block parameterized by a header and content. The parameters are here to + allow to split the header and rest of the block. *) +type ('header, 'content) block = { + header : 'header; (** Header of this block. *) + content : 'content; (** Content of the block. *) + initial_tick : Sc_rollup.Tick.t; + (** Initial tick of the PVM at this block, i.e. before evaluation of the + messages. *) + num_ticks : int64; + (** Number of ticks produced by the evaluation of the messages in this + block. *) +} + +(** The type of layer 2 blocks. This type corresponds to what is stored on disk + for L2 blocks. The contents is stored in separate tables because it can be + large to read is is not always necessary. *) +type t = (header, unit) block + +(** The type of layer 2 blocks including their content (inbox, messages, commitment). *) +type full = (header, content) block + +(** {2 Encodings} *) + +val header_encoding : header Data_encoding.t + +val header_size : int + +val content_encoding : content Data_encoding.t + +val block_encoding : + 'header Data_encoding.t -> + 'content Data_encoding.t -> + ('header, 'content) block Data_encoding.t + +val encoding : t Data_encoding.t + +val full_encoding : full Data_encoding.t + +(** {2 Helper functions} *) + +(** [most_recent_commitment header] returns the most recent commitment + information at the block of with [header]. It is either the commitment for + this block if there is one or the previous commitment otherwise. *) +val most_recent_commitment : header -> Sc_rollup.Commitment.Hash.t + +(** [final_tick block] is the final tick, after evaluation of the messages in + the [block], i.e. [block.initial_tick + block.num_ticks]. *) +val final_tick : ('a, 'b) block -> Sc_rollup.Tick.t diff --git a/src/proto_alpha/lib_sc_rollup/sc_rollup_context_hash.ml b/src/proto_alpha/lib_sc_rollup/sc_rollup_context_hash.ml new file mode 100644 index 0000000000000000000000000000000000000000..000536a07aa55fea3f79d2ce6506e8b777dab8bb --- /dev/null +++ b/src/proto_alpha/lib_sc_rollup/sc_rollup_context_hash.ml @@ -0,0 +1,41 @@ +(*****************************************************************************) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +open Tezos_crypto + +include + Blake2B.Make + (Base58) + (struct + let name = "Smart_rollup_context_hash" + + let title = "A base58-check encoded hash of a Smart rollup node context" + + let b58check_prefix = "\008\209\216\166" + + let size = None + end) + +let () = Base58.check_encoded_prefix b58check_encoding "SRCo" 54 diff --git a/src/proto_alpha/lib_sc_rollup/sc_rollup_context_hash.mli b/src/proto_alpha/lib_sc_rollup/sc_rollup_context_hash.mli new file mode 100644 index 0000000000000000000000000000000000000000..b8abde35922ae94993464853ef4e790436d18dbb --- /dev/null +++ b/src/proto_alpha/lib_sc_rollup/sc_rollup_context_hash.mli @@ -0,0 +1,26 @@ +(*****************************************************************************) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +include Tezos_crypto.S.HASH diff --git a/src/proto_alpha/lib_sc_rollup/sc_rollup_services.ml b/src/proto_alpha/lib_sc_rollup/sc_rollup_services.ml index 724f8a708599238249dcf17b26c46ce562f5e101..e9d32952a1d5c94004bf85de522677c92a1eefc3 100644 --- a/src/proto_alpha/lib_sc_rollup/sc_rollup_services.ml +++ b/src/proto_alpha/lib_sc_rollup/sc_rollup_services.ml @@ -402,6 +402,15 @@ module Global = struct let prefix = prefix / "block" /: Arg.block_id end) + let block = + Tezos_rpc.Service.get_service + ~description: + "Layer-2 block of the layer-2 chain with respect to a Layer 1 block \ + identifier" + ~query:Tezos_rpc.Query.empty + ~output:Sc_rollup_block.full_encoding + path + let hash = Tezos_rpc.Service.get_service ~description:"Tezos block hash of block known to the smart rollup node" diff --git a/tezt/lib_tezos/tezos_regression.ml b/tezt/lib_tezos/tezos_regression.ml index 16d4ed6de5deead6d7735aad5d6ae0bdb21ced39..04778279e42070f6e3c180c672ddefd09c1444ea 100644 --- a/tezt/lib_tezos/tezos_regression.ml +++ b/tezt/lib_tezos/tezos_regression.ml @@ -35,6 +35,7 @@ let replace_variables string = Remove this regexp as soon as the WASM PVM stabilizes. *) ("srs\\w{51}\\b", "[SC_ROLLUP_PVM_STATE_HASH]"); ("\\bB\\w{50}\\b", "[BLOCK_HASH]"); + ("SRCo\\w{50}\\b", "[SC_ROLLUP_CONTEXT_HASH]"); ("Co\\w{50}\\b", "[CONTEXT_HASH]"); ("txi\\w{50}\\b", "[TX_ROLLUP_INBOX_HASH]"); ("txmr\\w{50}\\b", "[TX_ROLLUP_MESSAGE_RESULT_HASH]"); diff --git a/tezt/tests/expected/sc_rollup.ml/Alpha- arith - RPC API should work and be stable.out b/tezt/tests/expected/sc_rollup.ml/Alpha- arith - RPC API should work and be stable.out index 8539b800bb716fa5805936e2f87a99dc895ede7d..1b3cd0c7969565346736e9d11bc969f5124f5f72 100644 --- a/tezt/tests/expected/sc_rollup.ml/Alpha- arith - RPC API should work and be stable.out +++ b/tezt/tests/expected/sc_rollup.ml/Alpha- arith - RPC API should work and be stable.out @@ -241,7 +241,7 @@ This sequence of operations was run: 16 ./octez-smart-rollup-client-alpha rpc get /global/block/head/num_messages -"8" +"5" ./octez-smart-rollup-client-alpha rpc get /global/block/head/status "Waiting for input message" @@ -260,3 +260,28 @@ This sequence of operations was run: ./octez-smart-rollup-client-alpha rpc get /global/tezos_level 18 + +./octez-smart-rollup-client-alpha rpc get /global/block/head +{ "block_hash": "[BLOCK_HASH]", + "level": 18, + "predecessor": "[BLOCK_HASH]", + "commitment_hash": null, + "previous_commitment_hash": + "[SC_ROLLUP_COMMITMENT_HASH]", + "context": "[SC_ROLLUP_CONTEXT_HASH]", + "inbox_witness": "[SC_ROLLUP_INBOX_MERKELIZED_PAYLOAD_HASHES_HASH]", + "inbox_hash": "[SC_ROLLUP_INBOX_HASH]", + "inbox": + { "level": 18, + "old_levels_messages": + { "index": "17", + "content": + { "hash": + "[SC_ROLLUP_INBOX_MERKELIZED_PAYLOAD_HASHES_HASH]", + "level": 18 }, + "back_pointers": + [ "[SC_ROLLUP_INBOX_HASH]", + "[SC_ROLLUP_INBOX_HASH]", + "[SC_ROLLUP_INBOX_HASH]" ] } }, + "messages": [ "31352d31", "31352d32", "31352d33", "31352d34", "31352d35" ], + "initial_tick": "352", "num_ticks": "28" } diff --git a/tezt/tests/expected/sc_rollup.ml/Alpha- wasm_2_0_0 - RPC API should work and be stable.out b/tezt/tests/expected/sc_rollup.ml/Alpha- wasm_2_0_0 - RPC API should work and be stable.out index b4b7b1f5397fb85ec354c074904db99f67995526..2634a5b2c83fe254defa4003c54a4b860604052f 100644 --- a/tezt/tests/expected/sc_rollup.ml/Alpha- wasm_2_0_0 - RPC API should work and be stable.out +++ b/tezt/tests/expected/sc_rollup.ml/Alpha- wasm_2_0_0 - RPC API should work and be stable.out @@ -241,7 +241,7 @@ This sequence of operations was run: 16 ./octez-smart-rollup-client-alpha rpc get /global/block/head/num_messages -"8" +"5" ./octez-smart-rollup-client-alpha rpc get /global/block/head/status "Waiting for input message" @@ -260,3 +260,28 @@ This sequence of operations was run: ./octez-smart-rollup-client-alpha rpc get /global/tezos_level 18 + +./octez-smart-rollup-client-alpha rpc get /global/block/head +{ "block_hash": "[BLOCK_HASH]", + "level": 18, + "predecessor": "[BLOCK_HASH]", + "commitment_hash": null, + "previous_commitment_hash": + "[SC_ROLLUP_COMMITMENT_HASH]", + "context": "[SC_ROLLUP_CONTEXT_HASH]", + "inbox_witness": "[SC_ROLLUP_INBOX_MERKELIZED_PAYLOAD_HASHES_HASH]", + "inbox_hash": "[SC_ROLLUP_INBOX_HASH]", + "inbox": + { "level": 18, + "old_levels_messages": + { "index": "17", + "content": + { "hash": + "[SC_ROLLUP_INBOX_MERKELIZED_PAYLOAD_HASHES_HASH]", + "level": 18 }, + "back_pointers": + [ "[SC_ROLLUP_INBOX_HASH]", + "[SC_ROLLUP_INBOX_HASH]", + "[SC_ROLLUP_INBOX_HASH]" ] } }, + "messages": [ "31352d31", "31352d32", "31352d33", "31352d34", "31352d35" ], + "initial_tick": "1430000000000", "num_ticks": "99000000000" } diff --git a/tezt/tests/sc_rollup.ml b/tezt/tests/sc_rollup.ml index 84060a5e6de8ad61977dbb409f77d68fb90a9582..09e603299b6b993501e01648a91bf95eb83d775b 100644 --- a/tezt/tests/sc_rollup.ml +++ b/tezt/tests/sc_rollup.ml @@ -1400,17 +1400,17 @@ let mode_publish mode publishes _protocol sc_rollup_node sc_rollup_client (if publishes then " have" else " never do so") ; unit -let commitment_not_stored_if_non_final _protocol sc_rollup_node sc_rollup_client - sc_rollup _node client = +let commitment_not_published_if_non_final _protocol sc_rollup_node + sc_rollup_client sc_rollup _node client = (* The rollup is originated at level `init_level`, and it requires `sc_rollup_commitment_period_in_blocks` levels to store a commitment. - There is also a delay of `block_finality_time` before storing a + There is also a delay of `block_finality_time` before publishing a commitment, to avoid including wrong commitments due to chain - reorganisations. Therefore the commitment will be stored and published + reorganisations. Therefore the commitment will be published when the [Commitment] module processes the block at level `init_level + sc_rollup_commitment_period_in_blocks + - levels_to_finalise`. At the level before, the commitment will not be - neither stored nor published. + levels_to_finalise`. At the level before, the commitment will be + neither stored but not published. *) let* genesis_info = RPC.Client.call ~hooks client @@ -1449,7 +1449,7 @@ let commitment_not_stored_if_non_final _protocol sc_rollup_node sc_rollup_client Sc_rollup_client.last_stored_commitment ~hooks sc_rollup_client in let stored_inbox_level = Option.map inbox_level commitment in - Check.(stored_inbox_level = None) + Check.(stored_inbox_level = Some store_commitment_level) (Check.option Check.int) ~error_msg: "Commitment has been stored at a level different than expected (%L = %R)" ; @@ -3735,14 +3735,14 @@ let test_rpcs ~kind = let l2_block_hash = JSON.as_string l2_block_hash in Check.((l1_block_hash = l2_block_hash) string) ~error_msg:"Head on L1 is %L where as on L2 it is %R" ; - let* l1_block_hash = + let* l1_block_hash_5 = RPC.Client.call client @@ RPC.get_chain_block_hash ~block:"5" () in - let*! l2_block_hash = + let*! l2_block_hash_5 = Sc_rollup_client.rpc_get ~hooks sc_client ["global"; "block"; "5"; "hash"] in - let l2_block_hash = JSON.as_string l2_block_hash in - Check.((l1_block_hash = l2_block_hash) string) + let l2_block_hash_5 = JSON.as_string l2_block_hash_5 in + Check.((l1_block_hash_5 = l2_block_hash_5) string) ~error_msg:"Block 5 on L1 is %L where as on L2 it is %R" ; let*! l2_finalied_block_level = Sc_rollup_client.rpc_get @@ -3760,7 +3760,7 @@ let test_rpcs ~kind = ["global"; "block"; "head"; "num_messages"] in let l2_num_messages = JSON.as_int l2_num_messages in - Check.((l2_num_messages = batch_size + 3) int) + Check.((l2_num_messages = batch_size) int) ~error_msg:"Number of messages of head is %L but should be %R" ; let*! _status = Sc_rollup_client.rpc_get @@ -3793,6 +3793,12 @@ let test_rpcs ~kind = let*! _level = Sc_rollup_client.rpc_get ~hooks sc_client ["global"; "tezos_level"] in + let*! l2_block = + Sc_rollup_client.rpc_get ~hooks sc_client ["global"; "block"; "head"] + in + let l2_block_hash' = JSON.(l2_block |-> "block_hash" |> as_string) in + Check.((l2_block_hash' = l2_block_hash) string) + ~error_msg:"L2 head is from full block is %L but should be %R" ; unit let test_messages_processed_by_commitment ~kind = @@ -4003,7 +4009,7 @@ let register ~kind ~protocols = ~kind ; test_commitment_scenario ~variant:"non_final_level" - commitment_not_stored_if_non_final + commitment_not_published_if_non_final protocols ~kind ; test_commitment_scenario