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 be457b5de8bbab7fff6afdf2411ffadd34a07c6f..221117c2534a0c9b49766a6544625a312b06ab69 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 705a988ea575aee54a25692a61ca7404aef7f2e2..8e0868ad9e7b7dccfa759a1a28ba1a106bf78b67 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 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 0000000000000000000000000000000000000000..b9ddcc0e1c9792658bdcec3c6dd0101a884f1115 --- /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 d8e3c6a7ebb851dcd494382d083c2d608fa06b0a..2f3e762fc8b1fc2018e2b4eba58d356ca7f2115a 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 365c02d67e7108a7118ac6ecad29b2949633d72b..4abc1fd1171190348b32ea27c057ca70ebfb4331 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"