From 667cbbfdec6b5103387b80264d242a01b436f6b6 Mon Sep 17 00:00:00 2001 From: Emma Turner Date: Wed, 20 Apr 2022 09:09:18 +0100 Subject: [PATCH 1/2] Tezt/Scoru: add regression test for inbox current messages hash --- .../sc_rollup_inbox_current_messages_hash.out | 144 +++++++++++++++ ...lup_inbox.out => sc_rollup_inbox_size.out} | 2 +- tezt/tests/sc_rollup.ml | 174 +++++++++++++++++- 3 files changed, 315 insertions(+), 5 deletions(-) create mode 100644 tezt/_regressions/sc_rollup_inbox_current_messages_hash.out rename tezt/_regressions/{sc_rollup_inbox.out => sc_rollup_inbox_size.out} (99%) diff --git a/tezt/_regressions/sc_rollup_inbox_current_messages_hash.out b/tezt/_regressions/sc_rollup_inbox_current_messages_hash.out new file mode 100644 index 000000000000..b9ddcc0e1c97 --- /dev/null +++ b/tezt/_regressions/sc_rollup_inbox_current_messages_hash.out @@ -0,0 +1,144 @@ +sc_rollup_inbox_current_messages_hash.out + +./tezos-client --wait none originate sc rollup from '[PUBLIC_KEY_HASH]' of kind arith booting with --burn-cap 9999999 +Node is bootstrapped. +Estimated gas: 1600.648 units (will add 100 for safety) +Estimated storage: 6522 bytes added (will add 20 for safety) +Operation successfully injected in the node. +Operation hash is '[OPERATION_HASH]' +NOT waiting for the operation to be included. +Use command + tezos-client wait for [OPERATION_HASH] to be included --confirmations 1 --branch [BLOCK_HASH] +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: [PUBLIC_KEY_HASH] + Fee to the baker: ꜩ0.000402 + Expected counter: 1 + Gas limit: 1701 + Storage limit: 6542 bytes + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ0.000402 + payload fees(the block proposer) ....... +ꜩ0.000402 + Originate smart contract rollup of kind arith with boot sector '' + This smart contract rollup origination was successfully applied + Consumed gas: 1600.648 + Storage size: 6522 bytes + Address: [SC_ROLLUP_HASH] + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ1.6305 + storage fees ........................... +ꜩ1.6305 + + +./tezos-client --wait none send sc rollup message 'text:["hello, message number 0", "hello, message number 1", "hello, message number 2", "hello, message number 3", "hello, message number 4"]' from bootstrap1 to '[SC_ROLLUP_HASH]' +Node is bootstrapped. +Estimated gas: 1651.024 units (will add 100 for safety) +Estimated storage: no bytes added +Operation successfully injected in the node. +Operation hash is '[OPERATION_HASH]' +NOT waiting for the operation to be included. +Use command + tezos-client wait for [OPERATION_HASH] to be included --confirmations 1 --branch [BLOCK_HASH] +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: [PUBLIC_KEY_HASH] + Fee to the baker: ꜩ0.00058 + Expected counter: 2 + Gas limit: 1752 + Storage limit: 0 bytes + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ0.00058 + payload fees(the block proposer) ....... +ꜩ0.00058 + Add a message to the inbox of the smart contract rollup at address [SC_ROLLUP_HASH] + This operation sending a message to a smart contract rollup was successfully applied + Consumed gas: 1651.152 + Resulting inbox state: + rollup = [SC_ROLLUP_HASH] + level = 3 + current messages hash = CoWX5Ej2i6inrCyWYcbiwSyHu7PVyomazXcTYG41GwDoVvqJtmda + nb_available_messages = 5 + message_counter = 5 + old_levels_messages = + content = CoUkdBQ53N7FWav8LuTvrcp3jyoxnpqk3xnEo3gSCgNwia4fq44j + index = 1 + back_pointers = CoVawGHT9AxoKnd7hDBCii5PEcM2U3WbtL4L5HGD6PC9BWcLnzqD + + + + +./tezos-client --wait none send sc rollup message 'text:["hello, message number 5", "hello, message number 6", "hello, message number 7", "hello, message number 8", "hello, message number 9", "hello, message number 10"]' from bootstrap1 to '[SC_ROLLUP_HASH]' +Node is bootstrapped. +Estimated gas: 1651.216 units (will add 100 for safety) +Estimated storage: no bytes added +Operation successfully injected in the node. +Operation hash is '[OPERATION_HASH]' +NOT waiting for the operation to be included. +Use command + tezos-client wait for [OPERATION_HASH] to be included --confirmations 1 --branch [BLOCK_HASH] +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: [PUBLIC_KEY_HASH] + Fee to the baker: ꜩ0.000608 + Expected counter: 3 + Gas limit: 1752 + Storage limit: 0 bytes + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ0.000608 + payload fees(the block proposer) ....... +ꜩ0.000608 + Add a message to the inbox of the smart contract rollup at address [SC_ROLLUP_HASH] + This operation sending a message to a smart contract rollup was successfully applied + Consumed gas: 1651.344 + Resulting inbox state: + rollup = [SC_ROLLUP_HASH] + level = 4 + current messages hash = CoVFYQa6ArFesQ42ivShv42ZGpbStrYmo8yi4wXNnXqXLd3yrfzy + nb_available_messages = 11 + message_counter = 6 + old_levels_messages = + content = CoWX5Ej2i6inrCyWYcbiwSyHu7PVyomazXcTYG41GwDoVvqJtmda + index = 2 + back_pointers = CoUmDifn9cHq3g1wRc8ft64oMz7Jha8f4mcUWZd2YRseVae6MQAN + CoUmDifn9cHq3g1wRc8ft64oMz7Jha8f4mcUWZd2YRseVae6MQAN + + + + +./tezos-client --wait none send sc rollup message 'text:[]' from bootstrap1 to '[SC_ROLLUP_HASH]' +Node is bootstrapped. +Estimated gas: 1651.408 units (will add 100 for safety) +Estimated storage: no bytes added +Operation successfully injected in the node. +Operation hash is '[OPERATION_HASH]' +NOT waiting for the operation to be included. +Use command + tezos-client wait for [OPERATION_HASH] to be included --confirmations 1 --branch [BLOCK_HASH] +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: [PUBLIC_KEY_HASH] + Fee to the baker: ꜩ0.000445 + Expected counter: 4 + Gas limit: 1752 + Storage limit: 0 bytes + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ0.000445 + payload fees(the block proposer) ....... +ꜩ0.000445 + Add a message to the inbox of the smart contract rollup at address [SC_ROLLUP_HASH] + This operation sending a message to a smart contract rollup was successfully applied + Consumed gas: 1651.408 + Resulting inbox state: + rollup = [SC_ROLLUP_HASH] + level = 5 + current messages hash = CoUkdBQ53N7FWav8LuTvrcp3jyoxnpqk3xnEo3gSCgNwia4fq44j + nb_available_messages = 11 + message_counter = 0 + old_levels_messages = + content = CoVFYQa6ArFesQ42ivShv42ZGpbStrYmo8yi4wXNnXqXLd3yrfzy + index = 3 + back_pointers = CoVFjqCQTXn2Paa8qshaSQjLN3ps7TJF2419fya4sK6UdveSwHTa + CoUmDifn9cHq3g1wRc8ft64oMz7Jha8f4mcUWZd2YRseVae6MQAN + + + diff --git a/tezt/_regressions/sc_rollup_inbox.out b/tezt/_regressions/sc_rollup_inbox_size.out similarity index 99% rename from tezt/_regressions/sc_rollup_inbox.out rename to tezt/_regressions/sc_rollup_inbox_size.out index d8e3c6a7ebb8..2f3e762fc8b1 100644 --- a/tezt/_regressions/sc_rollup_inbox.out +++ b/tezt/_regressions/sc_rollup_inbox_size.out @@ -1,4 +1,4 @@ -sc_rollup_inbox.out +sc_rollup_inbox_size.out ./tezos-client --wait none originate sc rollup from '[PUBLIC_KEY_HASH]' of kind arith booting with --burn-cap 9999999 Node is bootstrapped. diff --git a/tezt/tests/sc_rollup.ml b/tezt/tests/sc_rollup.ml index 365c02d67e71..4abc1fd11711 100644 --- a/tezt/tests/sc_rollup.ml +++ b/tezt/tests/sc_rollup.ml @@ -342,13 +342,13 @@ let get_inbox_from_sc_rollup_node sc_rollup_node = | None -> failwith "Unable to retrieve inbox from sc rollup node" | Some inbox -> parse_inbox inbox -let test_rollup_inbox = - let output_file _ = "sc_rollup_inbox" in +let test_rollup_inbox_size = + let output_file _ = "sc_rollup_inbox_size" in test ~__FILE__ ~output_file ~tags:["inbox"] - "pushing messages in the inbox" + "pushing messages in the inbox - check inbox size" (fun protocol -> setup ~protocol @@ fun node client -> ( with_fresh_rollup @@ fun sc_rollup_address _sc_rollup_node _filename -> @@ -365,6 +365,171 @@ let test_rollup_inbox = node client) +module Sc_rollup_inbox = struct + open Tezos_context_encoding.Context + + module Store = struct + module Maker = Irmin_pack_mem.Maker (Conf) + include Maker.Make (Schema) + module Schema = Tezos_context_encoding.Context.Schema + end + + include Tezos_context_helpers.Context.Make_tree (Conf) (Store) + + (* + The hash for empty messages is the hash of empty bytes, and not of an empty + tree. + + The hash for non-empty messages is the hash of the tree, where each message + payload sits at the key [[message_index, "payload"]], where [message_index] + is the index of the current message relative to the first message. + + The [message_counter] is reset to zero when the inbox level increments (and + therefore [current_messages] are zero-indexed in the tree). + *) + let rec build_current_messages_tree counter tree messages = + match messages with + | [] -> return tree + | message :: rest -> + let key = Data_encoding.Binary.to_string_exn Data_encoding.z counter in + let payload = Bytes.of_string message in + let* tree = add tree [key; "payload"] payload in + build_current_messages_tree (Z.succ counter) tree rest + + let predict_current_messages_hash = function + | [] -> return @@ Tezos_crypto.Context_hash.hash_bytes [] + | current_messages -> + let open Lwt.Syntax in + let+ tree = + build_current_messages_tree Z.zero (empty ()) current_messages + in + hash tree +end + +let fetch_messages_from_block sc_rollup_address client = + let* ops = RPC.get_operations client in + let messages = + ops |> JSON.as_list + |> List.concat_map JSON.as_list + |> List.concat_map (fun op -> JSON.(op |-> "contents" |> as_list)) + |> List.filter_map (fun op -> + if + JSON.(op |-> "kind" |> as_string) = "sc_rollup_add_messages" + && JSON.(op |-> "rollup" |> as_string) = sc_rollup_address + then Some JSON.(op |-> "message" |> as_list) + else None) + |> List.hd + |> List.map (fun message -> JSON.(message |> as_string)) + in + return messages + +let test_rollup_inbox_current_messages_hash = + let output_file _ = "sc_rollup_inbox_current_messages_hash" in + test + ~__FILE__ + ~output_file + ~tags:["inbox"] + "pushing messages in the inbox - current messages hash" + (fun protocol -> + setup ~protocol @@ fun node client -> + ( with_fresh_rollup @@ fun sc_rollup_address _sc_rollup_node _filename -> + let gen_message_batch from until = + List.map + (fun x -> + Printf.sprintf "hello, message number %s" (Int.to_string x)) + (range from until) + in + let prepare_batch messages = + messages + |> List.map (Printf.sprintf "\"%s\"") + |> String.concat ", " |> Printf.sprintf "text:[%s]" + in + let open Tezos_crypto.Context_hash in + (* no messages have been sent *) + let* (pristine_hash, _) = + get_inbox_from_tezos_node sc_rollup_address client + in + let* expected = Sc_rollup_inbox.predict_current_messages_hash [] in + let () = + Check.( + (to_b58check expected = pristine_hash) + string + ~error_msg:"expected pristine hash %L, got %R") + in + (* + send messages, and assert that + - the hash has changed + - the hash matches the 'predicted' hash from the messages we sent + *) + let fst_batch = gen_message_batch 0 4 in + let* () = + send_message client sc_rollup_address @@ prepare_batch fst_batch + in + let* (fst_batch_hash, _) = + get_inbox_from_tezos_node sc_rollup_address client + in + let () = + Check.( + (pristine_hash <> fst_batch_hash) + string + ~error_msg: + "expected current messages hash to change when messages sent") + in + let* expected = + Sc_rollup_inbox.predict_current_messages_hash fst_batch + in + let () = + Check.( + (to_b58check expected = fst_batch_hash) + string + ~error_msg:"expected first batch hash %L, got %R") + in + (* + send more messages, and assert that + - the messages can be retrieved from the latest block + - the hash matches the 'predicted' hash from the messages we sent + *) + let snd_batch = gen_message_batch 5 10 in + let* () = + send_message client sc_rollup_address @@ prepare_batch snd_batch + in + let* messages = fetch_messages_from_block sc_rollup_address client in + let () = + Check.( + (messages = snd_batch) + (list string) + ~error_msg:"expected messages:\n%R\nretrieved:\n%L") + in + let* (snd_batch_hash, _) = + get_inbox_from_tezos_node sc_rollup_address client + in + let* expected = + Sc_rollup_inbox.predict_current_messages_hash snd_batch + in + let () = + Check.( + (Tezos_crypto.Context_hash.to_b58check expected = snd_batch_hash) + string + ~error_msg:"expected second batch hash %L, got %R") + in + (* + send an empty list of messages, and assert that + - the hash matches the 'pristine' hash: a.k.a there are no 'current messages' + *) + let* () = send_message client sc_rollup_address @@ prepare_batch [] in + let* (empty_batch_hash, _) = + get_inbox_from_tezos_node sc_rollup_address client + in + let () = + Check.( + (pristine_hash = empty_batch_hash) + string + ~error_msg:"expected empty batch hash %L, got %R") + in + return () ) + node + client) + (* Synchronizing the inbox in the rollup node ------------------------------------------ @@ -638,7 +803,8 @@ let register ~protocols = test_rollup_client_gets_address protocols ; test_rollup_list protocols ; test_rollup_get_initial_level protocols ; - test_rollup_inbox protocols ; + test_rollup_inbox_size protocols ; + test_rollup_inbox_current_messages_hash protocols ; test_rollup_inbox_of_rollup_node "basic" basic_scenario protocols ; test_rollup_inbox_of_rollup_node "stops" -- GitLab From ef70c460df8b3401b53f9b4b6f460a971fae70c9 Mon Sep 17 00:00:00 2001 From: Emma Turner Date: Thu, 21 Apr 2022 15:04:51 +0100 Subject: [PATCH 2/2] Proto: Scoru: update sc rollup inbox hash generation docs additionally fix reference to sc_rollup_max_available_messages constant. --- src/proto_alpha/lib_protocol/sc_rollup_inbox_repr.ml | 7 +++++++ src/proto_alpha/lib_protocol/sc_rollup_inbox_repr.mli | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/proto_alpha/lib_protocol/sc_rollup_inbox_repr.ml b/src/proto_alpha/lib_protocol/sc_rollup_inbox_repr.ml index be457b5de8bb..221117c2534a 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_inbox_repr.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_inbox_repr.ml @@ -142,6 +142,13 @@ let pp_history_proof fmt cell = level, this archival process is applied until we reach the current level using an empty [current_messages]. See {!MakeHashingScheme.archive} for details. + + The [current_messages_hash] is either: + - the hash of 'empty bytes' when there are no current messages ; + - the root hash of the tree, where the contents of each message sit at the + key [[message_index, "payload"]], where [message_index] is the index of the + message in the list of [current_messages], if there are one or more + messages. *) type t = { diff --git a/src/proto_alpha/lib_protocol/sc_rollup_inbox_repr.mli b/src/proto_alpha/lib_protocol/sc_rollup_inbox_repr.mli index 705a988ea575..8e0868ad9e7b 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_inbox_repr.mli +++ b/src/proto_alpha/lib_protocol/sc_rollup_inbox_repr.mli @@ -54,7 +54,7 @@ message unprocessed by the rollup is always available. The number of available messages is bounded by - {!Constants_repr.sc_rollup_max_available_messages}. When an inbox + {!Constants_storage.sc_rollup_max_available_messages}. When an inbox reaches the maximum number of available messages, the inbox is said to be full and cannot accept more messages. This limitation is meant to ensure that Merkle proofs about the inbox contents have a -- GitLab