Raw File
node_event_level.ml
(*****************************************************************************)
(*                                                                           *)
(* Open Source License                                                       *)
(* Copyright (c) 2021 Nomadic Labs <contact@nomadic-labs.com>                *)
(*                                                                           *)
(* 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.                                                 *)
(*                                                                           *)
(*****************************************************************************)

(* Testing
   -------
   Component:    Node daemon
   Invocation:   dune exec tezt/tests/main.exe -- --file node_event_level.ml
   Subject:      Configuration of node event level. Incidentally, test
                 injection of transfer operations, baking, and a couple RPCs
                 that retrieve operations.
*)

let get_request_level = function
  | "debug" -> "request_completed_debug.v0"
  | "info" -> "request_completed_info.v0"
  | "notice" -> "request_completed_notice.v0"
  | level -> Test.fail "Incorrect %s level for request_completed event" level

(* Wait for a request event of the specified kind.
   Example of request of kind "inject":

     "event": {
       "request": {
         "request": "inject",
         "operation": {
           "branch": "BLMR9qLoCjnLrdc5br7sD7hZ6RfqfUZZaHgijQGkpzGeZFYm8ib",
           "data": "6c0002298c03ed7d454a101eb7022bc95f7e5f41ac78930301f70b00c0843d0000e7670f32038107a59a2b9cfefae36ea21f5aa63c006e4d3c29e6eaefddefead7225b2c2743afa3154de65393a5f6f857c15dc80b45b503a51eb30ead5d3e553050f66d001c48e4ce9e37eca415c3843f9143287c0f"
         }
       },
       "status": {
         "pushed": "2021-04-26T16:00:46.968-00:00",
         "treated": 1.9067e-05,
         "completed": 0.002623382
       }
     },
     "level": "notice"
*)
let wait_for_request_kind ~level (request_kind : string) node =
  let filter json =
    match JSON.(json |-> "view" |-> "request" |> as_string_opt) with
    | Some s when String.equal s request_kind -> Some ()
    | Some _ | None -> None
  in
  Node.wait_for node (get_request_level level) filter

(* Wait for the request signaling the injection of a new operation in
   the mempool. This event has level "info".
*)
let wait_for_injection = wait_for_request_kind ~level:"info" "inject"

(* Wait for the request signaling a flush of the state of the mempool.
   This event has level "notice".
*)
let wait_for_flush = wait_for_request_kind ~level:"info" "flush"

(* Wait for the request signaling the arrival of an operation in the mempool
   from a peer.
   Note: this event has level "debug", so the node needs to have event
   level set to "debug" for such an event to exist.
*)
let wait_for_arrival = wait_for_request_kind ~level:"debug" "arrived"

(* Inject a transfer operation from [client] and wait for an injection
   event on [node] (which should be the node associated to [client]).
*)
let transfer_and_wait_for_injection ?(wait_for_injection = wait_for_injection)
    node client amount_int giver_key receiver_key =
  let wait_for = wait_for_injection node in
  let* () =
    Client.transfer
      ~amount:(Tez.of_int amount_int)
      ~giver:Account.(giver_key.alias)
      ~receiver:Account.(receiver_key.alias)
      client
  in
  let* () = wait_for in
  unit

(* Bake for [client] and wait for a flush event on [node] (which should
   be the node associated to this client). If [level] is provided, also
   wait for the node to reach this level. A specific [mempool] can be provided.
*)
let bake_wait_log ?level ?protocol ?mempool ?ignore_node_mempool node client =
  let baked = wait_for_flush node in
  let* () =
    Client.bake_for_and_wait ?protocol ?mempool ?ignore_node_mempool client
  in
  let* _ = baked in
  Log.info "Baked." ;
  match level with
  | Some lvl ->
      let* _ = Node.wait_for_level node lvl in
      Log.info "Node at level %d." lvl ;
      unit
  | _ -> unit

(* Get the hash of an operation from the json representing the operation. *)
let get_hash op = JSON.(op |-> "hash" |> as_string)

(* Get the list of hashes of the mempool's validated operations (using
   RPC get /chains/main/mempool/pending_operations that provides all
   the operations in the mempool). *)
let get_validated_operation_hash_list client =
  let* pending_ops =
    Client.RPC.call client @@ RPC.get_chain_mempool_pending_operations ()
  in
  return (List.map get_hash JSON.(pending_ops |-> "validated" |> as_list))

(* Assert that [json] represents an empty list. *)
let check_json_is_empty_list ?(fail_msg = "") json =
  match JSON.(json |> as_list_opt) with
  | Some [] -> ()
  | _ ->
      let msg =
        if String.equal fail_msg "" then
          Format.sprintf "Expected an empty list, got: %s." (JSON.encode json)
        else fail_msg
      in
      Test.fail "%s" msg

(* Test.

   Aim: check that a node launched with "debug" event level performs
   various functions correctly: operation injection, baking, and a
   couple RPCs:
   - get /chains/main/mempool/pending_operations
     (RPC.get_mempool_pending_operations)
   - get /chains/main/blocks/head/operations (RPC.get_operations)

   Scenario:
   - Step 1: Start a node with event_level:debug, activate the protocol.
   - Step 2: Inject a transfer operation, test RPCs.
     2a) pending_operations should contain one validated operation.
     2b) operations in block should be empty.
   - Step 3: Bake, test RPCs.
     3a) pending_operations should be empty.
     3b) operations in block should contain the previously pending operation.
*)
let test_debug_level_misc =
  Protocol.register_test
    ~__FILE__
    ~title:"event level debug"
    ~tags:["node"; "event"]
  @@ fun protocol ->
  Log.info "Step 1: Start a node with event_level:debug, activate the protocol." ;
  let* node_1 = Node.init ~event_level:`Debug [Synchronisation_threshold 0] in
  let endpoint_1 = Client.(Node node_1) in
  let* client_1 = Client.init ~endpoint:endpoint_1 () in
  let* () = Client.activate_protocol_and_wait ~protocol client_1 in
  let* level = Node.get_level node_1 in
  Log.info "Node at level %d" level ;
  Log.info "Step 2: Inject a transfer operation, test RPCs." ;
  let* () =
    transfer_and_wait_for_injection
      node_1
      client_1
      1
      Constant.bootstrap1
      Constant.bootstrap2
  in
  Log.info "Injection done." ;
  Log.info "2a) pending_operations should contain one validated operation." ;
  let* validated_ophs = get_validated_operation_hash_list client_1 in
  Log.info "RPC.get_mempool_pending_operations done." ;
  let oph1 =
    match validated_ophs with
    | [x] -> x
    | _ -> Test.fail "Expected exactly one validated operation in mempool."
  in
  Log.info "Hash of injected operation: %s" oph1 ;
  Log.info "2b) operations in block should be empty." ;
  let* ops = Client.RPC.call client_1 @@ RPC.get_chain_block_operations () in
  Log.info "RPC.get_operations done." ;
  check_json_is_empty_list
    ?fail_msg:
      (Some
         "RPC.operations should return an empty list for the protocol \
          activation block.")
    ops ;
  Log.info "Step 3: Bake, test RPCs." ;
  let level = level + 1 in
  let* () = bake_wait_log ?level:(Some level) node_1 client_1 in
  Log.info "3a) pending_operations should be empty." ;
  let* validated_ophs = get_validated_operation_hash_list client_1 in
  Log.info "RPC.get_mempool_pending_operations done." ;
  (match validated_ophs with
  | [] -> ()
  | _ -> Test.fail "List of validated operations in mempool should be empty.") ;
  Log.info
    "3b) operations in block should contain the previously pending operation." ;
  let* ops = Client.RPC.call client_1 @@ RPC.get_chain_block_operations () in
  Log.info "RPC.get_operations done." ;
  (match JSON.(ops |> as_list_opt) with
  | Some [x1; x2; x3; x4] -> (
      List.iter check_json_is_empty_list [x1; x2; x3] ;
      match JSON.(x4 |> as_list_opt) with
      | Some [x] -> (
          match JSON.(x |-> "hash" |> as_string_opt) with
          | Some s when String.equal s oph1 -> ()
          | _ ->
              Test.fail
                "Fourth list returned by RPC.operations should contain only \
                 the previously validated operation.")
      | _ ->
          Test.fail
            "Fourth list returned by RPC.operations should contain exactly one \
             operation.")
  | _ -> Test.fail "RPC.operations should return a list of length 4.") ;
  unit

(* Wait for an event of name "set_head.v0".
   Note: this event has level "info", so the node needs to have event
   level set to either "debug" or "info" for such an event to exist.
*)
let wait_for_set_head node = Node.wait_for node "set_head.v0" (fun _ -> Some ())

(* Event handler that ensures there is no "set_head.v0" event (this event has
   level "info", so should not happen for nodes of event level "notice").
*)
let check_no_set_head event =
  if String.equal Node.(event.name) "set_head.v0" then
    Test.fail
      "Witnessed a set_head event (of level info), which should not happen for \
       a node configured with event level notice."

(* Assert that [incoming_level] is possible for events received from a
   node initialized with [config_level].
*)
let check_event_level ~config_level ~incoming_level =
  let config_level = Daemon.Level.to_string config_level in
  let to_int level =
    match String.lowercase_ascii level with
    | "debug" -> 0
    | "info" -> 1
    | "notice" -> 2
    | "warning" -> 3
    | "error" -> 4
    | "fatal" -> 5
    | _ ->
        Test.fail
          "Unexpected event level: %s (should be debug, info, notice, warning, \
           error, or fatal)."
          level
  in
  let config_int = to_int config_level
  and incoming_int = to_int incoming_level in
  if config_int >= 3 then
    Test.fail "Event level of a node should be notice, info, or debug." ;
  if config_int > incoming_int then
    Test.fail
      "Received an event of level %s while node was configured with event \
       level %s."
      incoming_level
      config_level ;
  ()

(* Ensure that all events of [node] have a level compatible with
   [config_level].
*)
let check_level_of_all_events node config_level =
  let handler event =
    match JSON.(Node.(event.value) |=> 1 |-> "level" |> as_string_opt) with
    | Some incoming_level -> check_event_level ~config_level ~incoming_level
    | None -> ()
  in
  Node.on_event node handler

(* Test.

   Aim: check that events of the right level are visible.

   Note: Possible levels are debug, info, notice, warning, error,
   and fatal. However, event level configuration for a node cannot
   be stricter than warning (see tezt/lib_tezos/node.mli:run).

   Scenario:
   - Step 1: Start three nodes with respective event levels debug, info, and
     notice.
   - Step 2: Setup event handlers to ensure that nodes never send events with
     level lower than their configuration.
   - Step 3: Connect the nodes and activate the protocol. Nodes 1 and 2
     should send a set_head event (of level info), but node 3 should not.
   - Step 4: inject a transfer operation from node 1, and witness an
     injection event from this node.
   - Step 5: Bake from node 3. Witness flush request (level notice) from all
     nodes, and set_head event (level info) from nodes 1 and 2.
   - Step 6: inject a transfer operations from nodes 2 and 3. Witness
     injection events (level notice) from these respective nodes, and operation
     arrival requests (level debug) from node 1.
*)
let test_event_levels =
  Protocol.register_test ~__FILE__ ~title:"event levels" ~tags:["node"; "event"]
  @@ fun protocol ->
  Log.info
    "Step 1: Start three nodes with respective event levels debug, info, and \
     notice." ;
  let node_1_event_level = `Debug in
  let node_2_event_level = `Info in
  let node_3_event_level = `Notice in
  let* node_1 =
    Node.init ~event_level:node_1_event_level [Synchronisation_threshold 0]
  and* node_2 =
    Node.init ~event_level:node_2_event_level [Synchronisation_threshold 0]
  and* node_3 =
    Node.init ~event_level:node_3_event_level [Synchronisation_threshold 0]
  in
  let endpoint_1 = Client.(Node node_1)
  and endpoint_2 = Client.(Node node_2)
  and endpoint_3 = Client.(Node node_3) in
  let* client_1 = Client.init ~endpoint:endpoint_1 ()
  and* client_2 = Client.init ~endpoint:endpoint_2 ()
  and* client_3 = Client.init ~endpoint:endpoint_3 () in
  Log.info
    "Step 2: Setup event handlers to ensure that nodes never send events with \
     level lower than their configuration." ;
  let () = check_level_of_all_events node_1 node_1_event_level in
  let () = check_level_of_all_events node_2 node_2_event_level in
  let () = check_level_of_all_events node_3 node_3_event_level in
  Log.info
    "Step 3: Connect the nodes and activate the protocol. Nodes 1 and 2 should \
     send a set_head event (of level info), but node 3 should not." ;
  let* () = Client.Admin.connect_address client_1 ~peer:node_2
  and* () = Client.Admin.connect_address client_2 ~peer:node_3 in
  let wait1a = wait_for_set_head node_1 in
  let wait2a = wait_for_set_head node_2 in
  let () = Node.on_event node_3 check_no_set_head in
  let* () = Client.activate_protocol ~protocol client_1 in
  Log.info "Protocol activated." ;
  let* () = wait1a in
  let* () = wait2a in
  Log.info "set_head received from nodes 1 and 2." ;
  let level = 1 in
  let* _ = Node.wait_for_level node_1 level
  and* _ = Node.wait_for_level node_2 level
  and* _ = Node.wait_for_level node_3 level in
  Log.info "All nodes at level %d." level ;
  Log.info
    "Step 4: inject a transfer operation from node 1, and witness an injection \
     event from this node." ;
  let* () =
    transfer_and_wait_for_injection
      node_1
      client_1
      1
      Constant.bootstrap1
      Constant.bootstrap2
  in
  Log.info "NOTHING HAPPENS" ;
  Log.info "Injection event received from node 1." ;
  Log.info
    "Step 5: Bake from node 3. Witness flush request (level notice) nodes, and \
     set_head event (level info) from nodes 1 and 2." ;
  let wait1b = wait_for_flush node_1 in
  let wait2b = wait_for_flush node_2 in
  let wait1c = wait_for_set_head node_1 in
  let wait2c = wait_for_set_head node_2 in
  let* () = Client.bake_for_and_wait client_3 in
  let* () = wait1b in
  let* () = wait2b in
  let* () = wait1c in
  let* () = wait2c in
  Log.info "Flush and set_head received." ;
  Log.info
    "Step 6: inject a transfer operations from nodes 2 and 3. Witness \
     injection events (level notice) from the respective nodes, and operation \
     arrival requests (level debug) from node 1." ;
  let wait1d = wait_for_arrival node_1 in
  let* () =
    transfer_and_wait_for_injection
      node_2
      client_2
      2
      Constant.bootstrap2
      Constant.bootstrap3
  in
  let* () = wait1d in
  Log.info "Injection and arrival received for first transfer." ;
  let wait1e = wait_for_arrival node_1 in
  Log.info "BEFORE TRANSFER" ;

  (* Since this node has its event at notice level we cannot use the
     request_completed_info event to wait for the injection. Instead we use the
     operation_injected event at notice level. *)
  let wait_for_injection node =
    Node.wait_for node "operation_injected.v0" (fun _ -> Some ())
  in
  let* () =
    transfer_and_wait_for_injection
      ~wait_for_injection
      node_3
      client_3
      3
      Constant.bootstrap3
      Constant.bootstrap4
  in
  Log.info "BEFORE ARRIVAL" ;
  let* () = wait1e in
  Log.info "Injection and arrival received for second transfer." ;
  unit

let register ~protocols =
  test_debug_level_misc protocols ;
  test_event_levels protocols
back to top