From 75bad42023f00e99fa101c37df095c5df69db62c Mon Sep 17 00:00:00 2001 From: "iguerNL@Functori" Date: Thu, 30 Oct 2025 17:00:14 +0100 Subject: [PATCH 1/8] Rollup node: extend loser mode's DAL case to emit logs about what's done --- src/lib_smart_rollup_node/loser_mode.ml | 66 +++++++++++++++---- src/lib_smart_rollup_node/loser_mode.mli | 2 +- .../lib_sc_rollup_node/fueled_pvm.ml | 12 ++-- 3 files changed, 62 insertions(+), 18 deletions(-) diff --git a/src/lib_smart_rollup_node/loser_mode.ml b/src/lib_smart_rollup_node/loser_mode.ml index a782a831531a..386995398a37 100644 --- a/src/lib_smart_rollup_node/loser_mode.ml +++ b/src/lib_smart_rollup_node/loser_mode.ml @@ -23,6 +23,30 @@ (* *) (*****************************************************************************) +module Event = struct + include Internal_event.Simple + + let section = ["smart_rollup_node"; "loser_mode"] + + let loser_mode_dal_decision = + declare_4 + ~section + ~name:"loser_mode_dal_decision" + ~msg: + "loser_mode {decision} published at level {published_level}, slot \ + {slot_index}, and page {page_index})" + ~level:Notice + ("published_level", Data_encoding.int32) + ("slot_index", Data_encoding.int31) + ("page_index", Data_encoding.int31) + ("decision", Data_encoding.string) + + let emit_dal_decision ~published_level ~slot_index ~page_index ~decision = + emit + loser_mode_dal_decision + (published_level, slot_index, page_index, decision) +end + type failure = {level : int; message_index : int; message_tick : int64} let failure_encoding = @@ -192,9 +216,10 @@ let is_invalid_dal_parameters = function let is_invalid_dal_page ~published_level ~slot_index ~page_index ~page_size ~honest_payload = + let open Lwt_syntax in let eq_or_wildcard v = function None -> true | Some w -> v = w in function - | Failure _ | Invalid_dal_parameters _ -> Either.left () + | Failure _ | Invalid_dal_parameters _ -> return @@ Either.left () | Invalid_dal_page { published_level = pl; @@ -202,19 +227,36 @@ let is_invalid_dal_page ~published_level ~slot_index ~page_index ~page_size page_index = pi; page_payload_strategy; } -> + let emit decision = + Event.emit_dal_decision + ~published_level + ~slot_index + ~page_index + ~decision + in if eq_or_wildcard published_level pl && eq_or_wildcard slot_index si && eq_or_wildcard page_index pi then - Either.right - @@ - match (page_payload_strategy, honest_payload) with - | `Flip, Some _ -> None - | `Flip, None -> Some (Bytes.make page_size 'L') - | `Alter, None -> None - | `Alter, Some data -> - let lll = Bytes.make page_size 'L' in - let zzz = Bytes.make page_size 'Z' in - Option.some (if Bytes.equal data lll then zzz else lll) - else Either.left () + let* res = + match (page_payload_strategy, honest_payload) with + | `Flip, Some _ -> + let+ () = emit "flipped " in + None + | `Flip, None -> + let+ () = emit "flipped " in + Some (Bytes.make page_size 'L') + | `Alter, None -> + let+ () = emit "cannot alter " in + None + | `Alter, Some data -> + let lll = Bytes.make page_size 'L' in + let zzz = Bytes.make page_size 'Z' in + let+ () = emit "altered " in + Option.some (if Bytes.equal data lll then zzz else lll) + in + return @@ Either.right res + else + let+ () = emit "didn't match" in + Either.left () diff --git a/src/lib_smart_rollup_node/loser_mode.mli b/src/lib_smart_rollup_node/loser_mode.mli index 8ac1c53cf1f7..7df4a76e264d 100644 --- a/src/lib_smart_rollup_node/loser_mode.mli +++ b/src/lib_smart_rollup_node/loser_mode.mli @@ -98,4 +98,4 @@ val is_invalid_dal_page : page_size:int -> honest_payload:bytes option -> t -> - (unit, bytes option) Either.t + (unit, bytes option) Either.t Lwt.t diff --git a/src/proto_alpha/lib_sc_rollup_node/fueled_pvm.ml b/src/proto_alpha/lib_sc_rollup_node/fueled_pvm.ml index f2f6a69c71dc..0db71dbf8e3a 100644 --- a/src/proto_alpha/lib_sc_rollup_node/fueled_pvm.ml +++ b/src/proto_alpha/lib_sc_rollup_node/fueled_pvm.ml @@ -166,13 +166,13 @@ module Make_fueled (F : Fuel.S) : FUELED_PVM with type fuel = F.t = struct (* This happens when, for example, the kernel requests a page from a future level. *) Lwt.fail (Error_wrapper error) | Ok data_opt -> ( - let data_opt = + let*! data_opt = let published_level = Raw_level.to_int32 dal_page.slot_id.published_level in let slot_index = Dal.Slot_index.to_int dal_page.slot_id.index in let page_index = dal_page.page_index in - match + let*! is_invalid_dal_page = Loser_mode.is_invalid_dal_page node_ctxt.config.loser_mode ~published_level @@ -180,9 +180,11 @@ module Make_fueled (F : Fuel.S) : FUELED_PVM with type fuel = F.t = struct ~page_index ~page_size:(Int64.to_int dal_parameters.page_size) ~honest_payload:data_opt - with - | Either.Left () -> data_opt - | Either.Right data_opt -> data_opt + in + Lwt.return + (match is_invalid_dal_page with + | Either.Left () -> data_opt + | Either.Right data_opt -> data_opt) in match data_opt with | None -> -- GitLab From 3f0bc230e4de759e55fd72fb6c2c54d43cf65951 Mon Sep 17 00:00:00 2001 From: "iguerNL@Functori" Date: Fri, 31 Oct 2025 08:38:20 +0100 Subject: [PATCH 2/8] Extend DAL loser mode to include the inbox level in the matched part and improve the syntax --- src/lib_smart_rollup_node/loser_mode.ml | 81 ++++++++++++++----- src/lib_smart_rollup_node/loser_mode.mli | 22 +++-- .../lib_sc_rollup_node/fueled_pvm.ml | 4 +- 3 files changed, 79 insertions(+), 28 deletions(-) diff --git a/src/lib_smart_rollup_node/loser_mode.ml b/src/lib_smart_rollup_node/loser_mode.ml index 386995398a37..93c51efb5f0c 100644 --- a/src/lib_smart_rollup_node/loser_mode.ml +++ b/src/lib_smart_rollup_node/loser_mode.ml @@ -29,22 +29,25 @@ module Event = struct let section = ["smart_rollup_node"; "loser_mode"] let loser_mode_dal_decision = - declare_4 + declare_5 ~section ~name:"loser_mode_dal_decision" ~msg: - "loser_mode {decision} published at level {published_level}, slot \ - {slot_index}, and page {page_index})" + "loser_mode {decision} at inbox level {inbox_level} for page \ + {page_index} of slot {slot_index} published at level \ + {published_level}" ~level:Notice + ("inbox_level", Data_encoding.int32) ("published_level", Data_encoding.int32) ("slot_index", Data_encoding.int31) ("page_index", Data_encoding.int31) ("decision", Data_encoding.string) - let emit_dal_decision ~published_level ~slot_index ~page_index ~decision = + let emit_dal_decision ~inbox_level ~published_level ~slot_index ~page_index + ~decision = emit loser_mode_dal_decision - (published_level, slot_index, page_index, decision) + (inbox_level, published_level, slot_index, page_index, decision) end type failure = {level : int; message_index : int; message_tick : int64} @@ -79,6 +82,7 @@ type dal_parameters = { (** See the mli file for the doc. *) type dal_page = { + inbox_level : int32 option; published_level : int32 option; slot_index : int option; page_index : int option; @@ -128,24 +132,32 @@ let encoding = case (Tag 2) ~title:"Invalid_dal_page" - (obj4 + (obj5 + (opt "inbox_level" int32) (opt "published_level" int32) (opt "slot_index" uint8) (opt "page_index" uint16) (req "page_payload_strategy" string)) (function | Invalid_dal_page - {published_level; slot_index; page_index; page_payload_strategy} - -> + { + inbox_level; + published_level; + slot_index; + page_index; + page_payload_strategy; + } -> Some - ( published_level, + ( inbox_level, + published_level, slot_index, page_index, page_payload_strategy_to_string page_payload_strategy ) | _ -> None) - (fun (published_level, slot_index, page_index, strat) -> + (fun (inbox_level, published_level, slot_index, page_index, strat) -> Invalid_dal_page { + inbox_level; published_level; slot_index; page_index; @@ -155,7 +167,18 @@ let encoding = let no_failures = Failure [] -let wildcard_or_value f str = match str with "*" -> None | _ -> Some (f str) +(* An example of reveal_dal_page, when used in tests: + + "reveal_dal_page published_level:15 slot_index:1 page_index:2 strategy:flip inbox_level:3" *) +let rec parse_reveal_dal_page_arg l f arg_name = + match l with + | [] -> None + | e :: l -> ( + match e with + | [name; value] when String.equal arg_name name -> + if value = "*" then None else Some (f value) + | [_; _] -> parse_reveal_dal_page_arg l f arg_name + | _ -> Stdlib.failwith "Unreachable") let make s = let tokens = String.split_on_char ' ' s in @@ -175,14 +198,31 @@ let make s = slot_size = Int64.of_string slot_size; page_size = Int64.of_string page_size; }) - | ["reveal_dal_page"; published_level; slot_index; page_index; strategy] -> + | "reveal_dal_page" :: parameters -> + let parameters = + List.map + (fun e -> + match String.split_on_char ':' e with + | [_; _] as res -> res + | _ -> + Stdlib.failwith + ("Argument " ^ e + ^ "provided to reveal_dal_page is not of the shape NAME:VALUE" + )) + parameters + in + let parse f arg_name = parse_reveal_dal_page_arg parameters f arg_name in Some (Invalid_dal_page { - published_level = wildcard_or_value Int32.of_string published_level; - slot_index = wildcard_or_value int_of_string slot_index; - page_index = wildcard_or_value int_of_string page_index; - page_payload_strategy = page_payload_strategy_of_string strategy; + inbox_level = parse Int32.of_string "inbox_level"; + published_level = parse Int32.of_string "published_level"; + slot_index = parse int_of_string "slot_index"; + page_index = parse int_of_string "page_index"; + page_payload_strategy = + Option.value + (parse page_payload_strategy_of_string "strategy") + ~default:`Flip; }) | _ -> ( let rec chop = function @@ -214,14 +254,15 @@ let is_invalid_dal_parameters = function | Failure _ | Invalid_dal_page _ -> None | Invalid_dal_parameters parameters -> Some parameters -let is_invalid_dal_page ~published_level ~slot_index ~page_index ~page_size - ~honest_payload = +let is_invalid_dal_page ~inbox_level ~published_level ~slot_index ~page_index + ~page_size ~honest_payload = let open Lwt_syntax in let eq_or_wildcard v = function None -> true | Some w -> v = w in function | Failure _ | Invalid_dal_parameters _ -> return @@ Either.left () | Invalid_dal_page { + inbox_level = il; published_level = pl; slot_index = si; page_index = pi; @@ -229,13 +270,15 @@ let is_invalid_dal_page ~published_level ~slot_index ~page_index ~page_size } -> let emit decision = Event.emit_dal_decision + ~inbox_level ~published_level ~slot_index ~page_index ~decision in if - eq_or_wildcard published_level pl + eq_or_wildcard inbox_level il + && eq_or_wildcard published_level pl && eq_or_wildcard slot_index si && eq_or_wildcard page_index pi then diff --git a/src/lib_smart_rollup_node/loser_mode.mli b/src/lib_smart_rollup_node/loser_mode.mli index 7df4a76e264d..1f84149bd53b 100644 --- a/src/lib_smart_rollup_node/loser_mode.mli +++ b/src/lib_smart_rollup_node/loser_mode.mli @@ -36,13 +36,16 @@ type dal_parameters = { (** DAL page selector and payload-forging strategy. Optional fields act as wildcards: - - [None] means "match any value" for that dimension. - Example: - { published_level = None; slot_index = Some 3; page_index = None; _ } + - [None] means "match any value" for that dimension. Example: + + { inbox_level = None; published_level = None; slot_index = Some 3; + page_index = None; _ } + matches every page of slot index 3 at any level. Fields: - - [published_level]: L1 level at which the slot was published (or [None]). + - [inbox_level]: L1 level at which the slot is being imported (or [None]). + - [published_level]: L1 level at which the slot was supposed to be published (or [None]). - [slot_index]: Index of the target slot at that level (or [None]). - [page_index]: Index of the target page within the slot (or [None]). - [page_payload_strategy]: How the faulty/losing node derives the payload @@ -52,6 +55,7 @@ type dal_parameters = { - [`Flip]: Toggle presence: [None] -> [Some bytes], [Some _] -> [None]. *) type dal_page = { + inbox_level : int32 option; published_level : int32 option; slot_index : int option; page_index : int option; @@ -81,10 +85,11 @@ val is_invalid_dal_parameters : t -> dal_parameters option (** Decide whether to corrupt a DAL page and, if so, how. - Given the current [published_level], [slot_index], [page_index], [page_size], - the honest page payload ([honest_payload]), and a failure plan [t], this - function checks whether an [Invalid_dal_page] rule matches (wildcards allowed - via [None] in the rule). If no rule applies, returns [Either.left ()]. + Given the current [inbox_level], [published_level], [slot_index], + [page_index], [page_size], the honest page payload ([honest_payload]), and a + failure plan [t], this function checks whether an [Invalid_dal_page] rule + matches (wildcards allowed via [None] in the rule). If no rule applies, + returns [Either.left ()]. If a rule applies, returns [Either.right forged]: - [forged = None] -> payload is removed (flip from [Some _] to [None]). @@ -92,6 +97,7 @@ val is_invalid_dal_parameters : t -> dal_parameters option See {!dal_page} above for forge strategies. *) val is_invalid_dal_page : + inbox_level:int32 -> published_level:int32 -> slot_index:int -> page_index:int -> diff --git a/src/proto_alpha/lib_sc_rollup_node/fueled_pvm.ml b/src/proto_alpha/lib_sc_rollup_node/fueled_pvm.ml index 0db71dbf8e3a..0f531e336a5b 100644 --- a/src/proto_alpha/lib_sc_rollup_node/fueled_pvm.ml +++ b/src/proto_alpha/lib_sc_rollup_node/fueled_pvm.ml @@ -150,12 +150,13 @@ module Make_fueled (F : Fuel.S) : FUELED_PVM with type fuel = F.t = struct is either [Request_dal_page] or [Request_adal_page] *) assert false in + let inbox_level = Int32.of_int level in let*! content = Dal_pages_request.page_content constants.dal ~dal_activation_level ~dal_attested_slots_validity_lag - ~inbox_level:(Int32.of_int level) + ~inbox_level node_ctxt dal_page in @@ -175,6 +176,7 @@ module Make_fueled (F : Fuel.S) : FUELED_PVM with type fuel = F.t = struct let*! is_invalid_dal_page = Loser_mode.is_invalid_dal_page node_ctxt.config.loser_mode + ~inbox_level ~published_level ~slot_index ~page_index -- GitLab From 1f354016050a3e17142364c53d51660029e5a73f Mon Sep 17 00:00:00 2001 From: "iguerNL@Functori" Date: Wed, 19 Nov 2025 17:41:50 +0100 Subject: [PATCH 3/8] DAL refutations loser mode generate random pages content --- src/lib_smart_rollup_node/loser_mode.ml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/lib_smart_rollup_node/loser_mode.ml b/src/lib_smart_rollup_node/loser_mode.ml index 93c51efb5f0c..463740cb65f8 100644 --- a/src/lib_smart_rollup_node/loser_mode.ml +++ b/src/lib_smart_rollup_node/loser_mode.ml @@ -254,6 +254,13 @@ let is_invalid_dal_parameters = function | Failure _ | Invalid_dal_page _ -> None | Invalid_dal_parameters parameters -> Some parameters +let make_random_bytes = + let alphabet = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + in + let len = String.length alphabet in + fun page_size -> Bytes.init page_size (fun _ -> alphabet.[Random.int len]) + let is_invalid_dal_page ~inbox_level ~published_level ~slot_index ~page_index ~page_size ~honest_payload = let open Lwt_syntax in @@ -289,13 +296,13 @@ let is_invalid_dal_page ~inbox_level ~published_level ~slot_index ~page_index None | `Flip, None -> let+ () = emit "flipped " in - Some (Bytes.make page_size 'L') + Some (make_random_bytes page_size) | `Alter, None -> let+ () = emit "cannot alter " in None | `Alter, Some data -> - let lll = Bytes.make page_size 'L' in - let zzz = Bytes.make page_size 'Z' in + let lll = make_random_bytes page_size in + let zzz = make_random_bytes page_size in let+ () = emit "altered " in Option.some (if Bytes.equal data lll then zzz else lll) in -- GitLab From 764ef5c104954e02fc6f86b764ca6841e7355dce Mon Sep 17 00:00:00 2001 From: "iguerNL@Functori" Date: Thu, 30 Oct 2025 08:14:06 +0100 Subject: [PATCH 4/8] Tezt/Rollups/DAL: add a toy kernel for DAL pages reveal --- tezt/lib_tezos/constant.ml | 15 +++ tezt/tests/kernels/echo_dal_reveal_pages.wabt | 108 ++++++++++++++++++ tezt/tests/kernels/echo_dal_reveal_pages.wasm | Bin 0 -> 470 bytes 3 files changed, 123 insertions(+) create mode 100644 tezt/tests/kernels/echo_dal_reveal_pages.wabt create mode 100644 tezt/tests/kernels/echo_dal_reveal_pages.wasm diff --git a/tezt/lib_tezos/constant.ml b/tezt/lib_tezos/constant.ml index 8c30e47a3ee7..98e118d7c5d6 100644 --- a/tezt/lib_tezos/constant.ml +++ b/tezt/lib_tezos/constant.ml @@ -158,6 +158,21 @@ module WASM = struct ~path:"tezt/tests/kernels/echo_dal_reveal_parameters.wasm" () + (* Toy kernel "echo_dal_reveal_pages". + + - On each kernel_run, asks to reveal exactly one DAL page: + published level = 15, slot = 1, page_index = 2. + + - The revealed bytes are written to the key "/dal/page" next to the pages + that were already written, if any. + + - No per-page loop, no L1-level watcher, and no strict size/error checks. *) + let echo_dal_reveal_pages = + Uses.make + ~tag:"echo_dal_reveal_pages" + ~path:"tezt/tests/kernels/echo_dal_reveal_pages.wasm" + () + let evm_kernel = Uses.make ~how_to_build:"make -f etherlink.mk build" diff --git a/tezt/tests/kernels/echo_dal_reveal_pages.wabt b/tezt/tests/kernels/echo_dal_reveal_pages.wabt new file mode 100644 index 000000000000..ca1fbce43c25 --- /dev/null +++ b/tezt/tests/kernels/echo_dal_reveal_pages.wabt @@ -0,0 +1,108 @@ +(module + (import "smart_rollup_core" "reveal" + (func $reveal (param i32 i32 i32 i32) (result i32))) + (import "smart_rollup_core" "store_write" + (func $store_write (param i32 i32 i32 i32 i32) (result i32))) + (import "smart_rollup_core" "store_value_size" + (func $store_value_size (param i32 i32) (result i32))) + + (memory 2) + (export "memory" (memory 0)) + + (global $PUBLISHED_LEVEL i32 (i32.const 15)) + (global $SLOT_INDEX i32 (i32.const 1)) + (global $NUM_PAGES i32 (i32.const 32)) + ;; CHANGEMENT MINIMAL: 3967 -> 4096 pour laisser la place complete a la page + (global $PAGE_SIZE i32 (i32.const 4096)) + (global $CHUNK i32 (i32.const 1024)) + + ;; Buffers + (global $REQ_PTR i32 (i32.const 64)) ;; payload 8B + (global $PAGE_BUF i32 (i32.const 128)) ;; page buffer + (data (i32.const 65536) "/dal/page") + (global $KEY_PTR i32 (i32.const 65536)) + (global $KEY_LEN i32 (i32.const 9)) + + (func $write_chunked (param $path_ptr i32) (param $path_len i32) + (param $base_off i32) (param $src_ptr i32) (param $len i32) + (result i32) + (local $written i32) (local $part i32) (local $rc i32) + (local.set $written (i32.const 0)) + (block $done + (loop $loop + (br_if $done (i32.ge_u (local.get $written) (local.get $len))) + (local.set $part + (select + (global.get $CHUNK) + (i32.sub (local.get $len) (local.get $written)) + (i32.gt_u (i32.sub (local.get $len) (local.get $written)) (global.get $CHUNK)) + ) + ) + (local.set $rc + (call $store_write + (local.get $path_ptr) (local.get $path_len) + (i32.add (local.get $base_off) (local.get $written)) + (i32.add (local.get $src_ptr) (local.get $written)) + (local.get $part) + ) + ) + (br_if $done (i32.lt_s (local.get $rc) (i32.const 0))) + (local.set $written (i32.add (local.get $written) (local.get $part))) + (br $loop) + ) + ) + (local.get $written) + ) + + (func (export "kernel_run") + (local $page_index i32) (local $len i32) (local $offset i32) (local $cur_len i32) + (local.set $page_index (i32.const 2)) + + ;; payload [0x02 | level(i32 BE) | slot(u8) | page(i16 BE)] + (i32.store8 (global.get $REQ_PTR) (i32.const 2)) + (i32.store8 (i32.add (global.get $REQ_PTR) (i32.const 1)) + (i32.shr_u (global.get $PUBLISHED_LEVEL) (i32.const 24))) + (i32.store8 (i32.add (global.get $REQ_PTR) (i32.const 2)) + (i32.shr_u (global.get $PUBLISHED_LEVEL) (i32.const 16))) + (i32.store8 (i32.add (global.get $REQ_PTR) (i32.const 3)) + (i32.shr_u (global.get $PUBLISHED_LEVEL) (i32.const 8))) + (i32.store8 (i32.add (global.get $REQ_PTR) (i32.const 4)) + (global.get $PUBLISHED_LEVEL)) + (i32.store8 (i32.add (global.get $REQ_PTR) (i32.const 5)) + (global.get $SLOT_INDEX)) + (i32.store8 (i32.add (global.get $REQ_PTR) (i32.const 6)) + (i32.shr_u (local.get $page_index) (i32.const 8))) + (i32.store8 (i32.add (global.get $REQ_PTR) (i32.const 7)) + (local.get $page_index)) + + ;; reveal vers PAGE_BUF, avec dst_max = 4096 + (local.set $len + (call $reveal + (global.get $REQ_PTR) (i32.const 8) + (global.get $PAGE_BUF) (global.get $PAGE_SIZE) + ) + ) + + ;; ecriture en append sous "/dal/page" + ;; cur_len = store_value_size("/dal/page") + (local.set $cur_len + (call $store_value_size + (global.get $KEY_PTR) (global.get $KEY_LEN) + ) + ) + ;; si cur_len < 0 (pas de valeur / erreur), on part de 0 + (local.set $offset + (select + (i32.const 0) + (local.get $cur_len) + (i32.lt_s (local.get $cur_len) (i32.const 0)) + ) + ) + (drop + (call $write_chunked + (global.get $KEY_PTR) (global.get $KEY_LEN) + (local.get $offset) (global.get $PAGE_BUF) (local.get $len) + ) + ) + ) +) diff --git a/tezt/tests/kernels/echo_dal_reveal_pages.wasm b/tezt/tests/kernels/echo_dal_reveal_pages.wasm new file mode 100644 index 0000000000000000000000000000000000000000..60ceec7096db50cfbc121010c922c87dc16514d1 GIT binary patch literal 470 zcmZQbEY4+QU|?XBW=UYFudlCXtWRJC(I5sBn9IPxl*lYtoSRrw5?_>`lT%s{pPXNm z%2t$GmYSHuzmU0jl1lp0@Nlv$F>z=$a?0Ff_C%qdNcFV3t=Wnf}vW@2P!WoBex zVl&~aXK>`_u4iy$1Q7}#q5(p3fY=8ZKx6}0vY~+m#Np&-7iVJ2P0h_Os$^o|%1$lH zOU;QdD$QeH;kwVrSjEVe8K21l3z_ACXc0uE(1Wo7{eMMh Date: Thu, 20 Nov 2025 06:23:51 +0100 Subject: [PATCH 5/8] Tezt/Rollups: provide the protocol to with_dal callback for refutation helpers --- tezt/lib_tezos/sc_rollup_helpers.ml | 2 +- tezt/lib_tezos/sc_rollup_helpers.mli | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tezt/lib_tezos/sc_rollup_helpers.ml b/tezt/lib_tezos/sc_rollup_helpers.ml index 3b99502120d7..c5680c2a8827 100644 --- a/tezt/lib_tezos/sc_rollup_helpers.ml +++ b/tezt/lib_tezos/sc_rollup_helpers.ml @@ -1009,7 +1009,7 @@ let test_refutation_scenario_aux ~(mode : Sc_rollup_node.mode) ~kind ?with_dal let* dal_node = match with_dal with | None -> Lwt.return_none - | Some init_dal_node -> init_dal_node node client + | Some init_dal_node -> init_dal_node protocol node client in let run_honest_node sc_rollup_node = diff --git a/tezt/lib_tezos/sc_rollup_helpers.mli b/tezt/lib_tezos/sc_rollup_helpers.mli index 4f1e4cf77f4e..3298232dd182 100644 --- a/tezt/lib_tezos/sc_rollup_helpers.mli +++ b/tezt/lib_tezos/sc_rollup_helpers.mli @@ -401,9 +401,9 @@ val reveal_hash : protocol:'a -> kind:string -> string -> reveal_hash val test_refutation_scenario_aux : mode:Sc_rollup_node.mode -> kind:string -> - ?with_dal:(Node.t -> Client.t -> Dal_node.t option Lwt.t) -> + ?with_dal:(Protocol.t -> Node.t -> Client.t -> Dal_node.t option Lwt.t) -> refutation_scenario_parameters -> - 'a -> + Protocol.t -> Sc_rollup_node.t -> string -> Node.t -> -- GitLab From 2a9ce649ec7b1854d0d0dbbeb25f3f06d82e73f2 Mon Sep 17 00:00:00 2001 From: "iguerNL@Functori" Date: Thu, 20 Nov 2025 06:31:03 +0100 Subject: [PATCH 6/8] Tezt/Rollups: ability to set DAL slots TTL in refutation helpers --- tezt/lib_tezos/sc_rollup_helpers.ml | 9 ++++++++- tezt/lib_tezos/sc_rollup_helpers.mli | 1 + tezt/tests/sc_rollup.ml | 17 +++++++++++------ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/tezt/lib_tezos/sc_rollup_helpers.ml b/tezt/lib_tezos/sc_rollup_helpers.ml index c5680c2a8827..4f0c9c5a7d8a 100644 --- a/tezt/lib_tezos/sc_rollup_helpers.ml +++ b/tezt/lib_tezos/sc_rollup_helpers.ml @@ -330,7 +330,8 @@ let make_string_parameter name = function let setup_l1 ?timestamp ?bootstrap_smart_rollups ?bootstrap_contracts ?commitment_period ?challenge_window ?timeout ?whitelist_enable ?rpc_external ?(riscv_pvm_enable = false) ?minimal_block_delay - ?dal_incentives ?dal_rewards_weight protocol = + ?dal_incentives ?dal_rewards_weight ?dal_attested_slots_validity_lag + protocol = let parameters = make_parameter "smart_rollup_commitment_period_in_blocks" commitment_period @ make_parameter "smart_rollup_challenge_window_in_blocks" challenge_window @@ -351,6 +352,12 @@ let setup_l1 ?timestamp ?bootstrap_smart_rollups ?bootstrap_contracts @ make_int_parameter ["issuance_weights"; "dal_rewards_weight"] dal_rewards_weight + @ make_int_parameter + [ + "smart_rollup_reveal_activation_level"; + "dal_attested_slots_validity_lag"; + ] + dal_attested_slots_validity_lag @ if riscv_pvm_enable then [(["smart_rollup_riscv_pvm_enable"], `Bool true)] else [] diff --git a/tezt/lib_tezos/sc_rollup_helpers.mli b/tezt/lib_tezos/sc_rollup_helpers.mli index 3298232dd182..f5eadd6fd6fc 100644 --- a/tezt/lib_tezos/sc_rollup_helpers.mli +++ b/tezt/lib_tezos/sc_rollup_helpers.mli @@ -154,6 +154,7 @@ val setup_l1 : ?minimal_block_delay:int -> ?dal_incentives:bool -> ?dal_rewards_weight:int -> + ?dal_attested_slots_validity_lag:int -> Protocol.t -> (Node.t * Client.t) Lwt.t diff --git a/tezt/tests/sc_rollup.ml b/tezt/tests/sc_rollup.ml index 4c92a0935705..ebbf7ca7f0bd 100644 --- a/tezt/tests/sc_rollup.ml +++ b/tezt/tests/sc_rollup.ml @@ -176,7 +176,8 @@ let test_full_scenario ?supports ?regression ?hooks ~kind ?mode ?boot_sector ?commitment_period ?(parameters_ty = "string") ?challenge_window ?timeout ?timestamp ?rollup_node_name ?whitelist_enable ?whitelist ?operator ?operators ?(uses = fun _protocol -> []) ?rpc_external ?allow_degraded - ?kernel_debug_log ?preimages_dir {variant; tags; description} scenario = + ?kernel_debug_log ?preimages_dir ?dal_attested_slots_validity_lag + {variant; tags; description} scenario = let uses protocol = (Constant.octez_smart_rollup_node :: Option.to_list preimages_dir) @ uses protocol @@ -201,6 +202,7 @@ let test_full_scenario ?supports ?regression ?hooks ~kind ?mode ?boot_sector ?timestamp ?whitelist_enable ~riscv_pvm_enable + ?dal_attested_slots_validity_lag protocol in let operator = @@ -3178,14 +3180,16 @@ let test_can_stake ~kind = let* _ = Sc_rollup_node.wait_sync rollup_node ~timeout:20. in unit -let test_refutation_scenario ?commitment_period ?challenge_window ~variant ~mode - ~kind ?(ci_disabled = false) ?uses ?(timeout = 60) ?timestamp ?boot_sector - ?(extra_tags = []) ?with_dal ({allow_degraded; _} as scenario) = +let test_refutation_scenario ?regression ?commitment_period ?challenge_window + ~variant ~mode ~kind ?(ci_disabled = false) ?uses ?(timeout = 60) ?timestamp + ?boot_sector ?(extra_tags = []) ?with_dal ?dal_attested_slots_validity_lag + ({allow_degraded; _} as scenario) = let regression = (* TODO: https://gitlab.com/tezos/tezos/-/issues/5313 Disabled dissection regressions for parallel games, as it introduces flakyness. *) - List.compare_length_with scenario.loser_modes 1 <= 0 + if Option.is_some regression then regression + else Some (List.compare_length_with scenario.loser_modes 1 <= 0) in let tags = ["refutation"] @ if mode = Sc_rollup_node.Accuser then ["accuser"] else [] @@ -3193,7 +3197,7 @@ let test_refutation_scenario ?commitment_period ?challenge_window ~variant ~mode let tags = if ci_disabled then Tag.ci_disabled :: tags else tags in let variant = variant ^ if mode = Accuser then "+accuser" else "" in test_full_scenario - ~regression + ?regression ?hooks:None (* We only want to capture dissections manually *) ?commitment_period ~kind @@ -3205,6 +3209,7 @@ let test_refutation_scenario ?commitment_period ?challenge_window ~variant ~mode ?challenge_window ~rollup_node_name:"honest" ~allow_degraded + ?dal_attested_slots_validity_lag { tags = tags @ extra_tags; variant = Some variant; -- GitLab From ed5d524a4c173500cdd825fc24f7f1e52dd47a44 Mon Sep 17 00:00:00 2001 From: "iguerNL@Functori" Date: Thu, 20 Nov 2025 06:31:46 +0100 Subject: [PATCH 7/8] Tezt/Rollups: Implement test strategies for refutation with DAL --- .../runtime-dependency-tags.out | 1 + tezt/tests/sc_rollup.ml | 149 ++++++++++++++++++ 2 files changed, 150 insertions(+) diff --git a/tezt/lib_wrapper/expected/tezt_wrapper.ml/runtime-dependency-tags.out b/tezt/lib_wrapper/expected/tezt_wrapper.ml/runtime-dependency-tags.out index 2a0a9de69d34..9de645d80429 100644 --- a/tezt/lib_wrapper/expected/tezt_wrapper.ml/runtime-dependency-tags.out +++ b/tezt/lib_wrapper/expected/tezt_wrapper.ml/runtime-dependency-tags.out @@ -42,6 +42,7 @@ riscv: src/riscv/assets/jstz.checksum riscv: src/riscv/assets/preimages riscv: src/riscv/assets/riscv-dummy.elf riscv: src/riscv/assets/riscv-dummy.elf.checksum +echo_dal_reveal_pages: tezt/tests/kernels/echo_dal_reveal_pages.wasm echo_dal_reveal_parameters: tezt/tests/kernels/echo_dal_reveal_parameters.wasm riscv: tezt/tests/riscv-tests/jstz-inbox.json alpha_json: tezt/tests/weeklynet_configs/alpha.json diff --git a/tezt/tests/sc_rollup.ml b/tezt/tests/sc_rollup.ml index ebbf7ca7f0bd..f97b44fed1fc 100644 --- a/tezt/tests/sc_rollup.ml +++ b/tezt/tests/sc_rollup.ml @@ -3453,6 +3453,154 @@ let test_invalid_dal_parameters protocols = ~priority:`Priority_honest) protocols +(* kernel_imported_publish_level is the imported published level hardcoded in + the [Constant.WASM.echo_dal_reveal_pages] kernel. *) +let with_dal_ready_for_echo_dal_reveal_pages ~operator_profiles:_ + ~kernel_imported_publish_level:_ ~may_publish_and_attest_slot:_ + ~expected_attestation_lag:_ = + fun _protocol _tezos_node _tezos_client -> + (* Will be implemented in the next commit *) + assert false + +type case = { + inbox_level : int; + player_priority : [`Priority_loser | `Priority_honest]; + attestation_status : [`Attested | `Unattested]; + (* TODO: We might want to distinguish "Published | Unpublished" for the + Unattested case in the future. *) +} + +let player_priority_to_string = function + | `Priority_loser -> "Priority_loser" + | `Priority_honest -> "Priority_honest" + +let attestation_status_to_string = function + | `Attested -> "Attested" + | `Unattested -> "Unattested" + +let test_refutation_with_dal_page_import protocols = + (* This is the published level for which the toy kernel + [Constant.WASM.echo_dal_reveal_pages] asks to import page 2 of slot 1. *) + let published_level = 15 in + (* This is the test's attestation lag (checked in the scenario). *) + let dal_lag = 5 in + (* This is the slot index at which we'll possibly publish a slot. *) + let slot_index = 1 in + (* Once a slot is attested, its import is valid for this number of + blocks. After that, any import is considered invalid. *) + let dal_ttl = 50 in + + (* First dimension of the tests: we vary inbox_level at which the divergence + between the honest and loser rollups will happen. This stress-tests various + corner cases regarding the level at which the import request is sent + w.r.t. published level. *) + let dimension_inbox_level = + [ + (* Test import around published_level. Nothing should be imported *) + published_level - 1; + published_level + 0; + published_level + 1; + (* Test import around attested_level. Nothing should be imported for the + first level below. For the two others, the page should be imported if + the slot is attested. *) + published_level + dal_lag - 1; + published_level + dal_lag + 0; + published_level + dal_lag + 1; + (* Test import attested_level + TLL. The slot's page should not be + imported for the third case below, even if it is attested, because its + TTL expired. *) + published_level + dal_ttl + dal_lag - 1; + published_level + dal_ttl + dal_lag + 0; + published_level + dal_ttl + dal_lag + 1; + ] + in + + (* Second dimension of the tests: Which player will start the game. This will + have an impact on which one will provide the final proof. *) + let dimension_player_priority = [`Priority_loser; `Priority_honest] in + + (* Third dimension of the tests: We will test both with attested an unattested + slots. *) + let dimension_attested = [`Attested; `Unattested] in + + (* The list of tests, as a cartesian product of 3 dimensions above. *) + let all_cases = + List.fold_left + (fun accu inbox_level -> + List.fold_left + (fun accu player_priority -> + List.fold_left + (fun accu attestation_status -> + {inbox_level; player_priority; attestation_status} :: accu) + accu + dimension_attested) + accu + dimension_player_priority) + [] + dimension_inbox_level + in + + List.iter + (fun {inbox_level; attestation_status; player_priority} -> + (* One test name for each dimensions combination. *) + let variant = + Format.sprintf + "dal_page_flipped_at_inbox_level_%d_%s_%s" + inbox_level + (player_priority_to_string player_priority) + (attestation_status_to_string attestation_status) + in + (* The behaviour of the loser mode is parameterized by the inbox level at + which the payload of imported page is flipped. *) + let loser_modes = + (* See src/lib_smart_rollup_node/loser_mode.mli for the semantics of this + loser mode. *) + [ + Format.sprintf + "reveal_dal_page published_level:15 slot_index:1 page_index:2 \ + strategy:flip inbox_level:%d" + inbox_level; + ] + in + let may_publish_and_attest_slot = + if attestation_status = `Unattested then None + else Some (published_level, slot_index) + in + let with_dal = + with_dal_ready_for_echo_dal_reveal_pages + ~operator_profiles:[slot_index] + ~kernel_imported_publish_level:published_level + ~expected_attestation_lag:dal_lag + ~may_publish_and_attest_slot + in + let priority = + (player_priority :> [`No_priority | `Priority_honest | `Priority_loser]) + in + test_refutation_scenario + ~uses:(fun _protocol -> + [Constant.WASM.echo_dal_reveal_pages; Constant.octez_dal_node]) + ~kind:"wasm_2_0_0" + ~mode:Operator + ~challenge_window:150 + ~timeout:120 + ~commitment_period:10 + ~dal_attested_slots_validity_lag:dal_ttl + ~variant + ~boot_sector: + (read_kernel + ~base:"" + ~suffix:"" + (Uses.path Constant.WASM.echo_dal_reveal_pages)) + (refutation_scenario_parameters + ~loser_modes + (inputs_for 10) + ~final_level:100 + ~priority) + protocols + ~regression:false + ~with_dal) + all_cases + (** Run one of the refutation tests with an accuser instead of a full operator. *) let test_accuser protocols = test_refutation_scenario @@ -7434,6 +7582,7 @@ let register_protocol_independent () = test_injector_auto_discard protocols ; test_accuser protocols ; test_invalid_dal_parameters protocols ; + test_refutation_with_dal_page_import protocols ; test_bailout_refutation protocols ; test_multiple_batcher_key ~kind protocols ; test_batcher_order_msgs ~kind protocols ; -- GitLab From ddc68b6f778e60e1dfd57868a5f97217fbda1e57 Mon Sep 17 00:00:00 2001 From: "iguerNL@Functori" Date: Thu, 20 Nov 2025 12:00:04 +0100 Subject: [PATCH 8/8] Tezt/Rollups: Implement with_dal_ready_for_echo_dal_reveal_pages to initialize DAL context for refutation tests --- tezt/tests/sc_rollup.ml | 106 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 100 insertions(+), 6 deletions(-) diff --git a/tezt/tests/sc_rollup.ml b/tezt/tests/sc_rollup.ml index f97b44fed1fc..a22e0b47a088 100644 --- a/tezt/tests/sc_rollup.ml +++ b/tezt/tests/sc_rollup.ml @@ -3455,12 +3455,106 @@ let test_invalid_dal_parameters protocols = (* kernel_imported_publish_level is the imported published level hardcoded in the [Constant.WASM.echo_dal_reveal_pages] kernel. *) -let with_dal_ready_for_echo_dal_reveal_pages ~operator_profiles:_ - ~kernel_imported_publish_level:_ ~may_publish_and_attest_slot:_ - ~expected_attestation_lag:_ = - fun _protocol _tezos_node _tezos_client -> - (* Will be implemented in the next commit *) - assert false +let with_dal_ready_for_echo_dal_reveal_pages ~operator_profiles + ~kernel_imported_publish_level ~may_publish_and_attest_slot + ~expected_attestation_lag = + fun protocol tezos_node tezos_client -> + (* We create and start a DAL node with the given producer profile. *) + let dal_node = Dal_node.create ~node:tezos_node () in + let* () = Dal_node.init_config ~operator_profiles dal_node in + let* () = Dal_node.run dal_node ~wait_ready:true in + let* () = + match may_publish_and_attest_slot with + | None -> + (* No slot to publish and atest *) + unit + | Some (published_level, slot_index) -> + (* We want to publish a slot at the given level and index, and then attest it. *) + let* curr_level = Node.get_level tezos_node in + if curr_level > published_level then + Test.fail + "publish level (%d) smaller than current level (%d)@." + published_level + curr_level ; + (* Advance the L1 chain until [published_level - 1]. *) + let* () = + Client.bake_for ~count:(published_level - curr_level - 1) tezos_client + in + let* _lvl = Node.wait_for_level tezos_node (published_level - 1) in + let* dal_parameters = Dal_common.Parameters.from_client tezos_client in + let attestation_lag = dal_parameters.attestation_lag in + let slot_size = dal_parameters.cryptobox.slot_size in + if expected_attestation_lag <> attestation_lag then + Test.fail + "Expected attestation_lag %d, got %d@." + expected_attestation_lag + attestation_lag ; + Dal.publish_store_and_attest_slot + ~protocol + tezos_client + tezos_node + dal_node + Constant.bootstrap1 + ~index:slot_index + ~content: + (Dal_common.Helpers.make_slot + ~slot_size + (String.make slot_size 'T')) + ~attestation_lag + ~number_of_slots:dal_parameters.number_of_slots + in + (* Whether we published and attested a slot or not, we advance the L1 chain + until [kernel_imported_publish_level + expected_attestation_lag] is + finalized to ensure that the DAL node can answer the rollup's requests + about the slot 1 published at level [kernel_imported_publish_level]. *) + let target_final_l1_level = + kernel_imported_publish_level + expected_attestation_lag + in + let* curr_level = Node.get_level tezos_node in + let block_finality = 2 in + let num_blocks_to_bake = + target_final_l1_level + block_finality - curr_level + in + let* () = + if num_blocks_to_bake <= 0 then unit + else + let wait_for_target_final_l1_level = + Dal_node.wait_for dal_node "dal_new_L1_final_block.v0" (fun e -> + if JSON.(e |-> "level" |> as_int) = target_final_l1_level then + Some () + else None) + in + let* () = + (* [Client.bake_for ~count] doesn't work here / is flaky. *) + repeat num_blocks_to_bake (fun () -> Client.bake_for tezos_client) + in + wait_for_target_final_l1_level + in + (* Ensure that in case we wanted an attested slot, it's indeed attested + (otherwise, we'd not test the desired configuration). *) + let* () = + match may_publish_and_attest_slot with + | None -> unit + | Some (published_level, slot_index) -> + let open Dal_common.RPC in + let open Dal_common.RPC.Local in + let* slot_status = + call dal_node + @@ get_level_slot_status ~slot_level:published_level ~slot_index + in + Check.(slot_status = Attested expected_attestation_lag) + ~__LOC__ + Dal_common.Check.slot_id_status_typ + ~error_msg: + "The value of the fetched status should match the expected one \ + (current = %L, expected = %R)" ; + Log.info + "Slot published at level %d and index %d is attested as expected@." + published_level + slot_index ; + unit + in + Lwt.return_some dal_node type case = { inbox_level : int; -- GitLab