Raw File
multinode_snapshot.ml
(*****************************************************************************)
(*                                                                           *)
(* Open Source License                                                       *)
(* Copyright (c) 2022 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: Snapshot and store
   Invocation: dune exec tezt/tests/main.exe -- -f multinode_snapshot.ml
   Subject: Tests both the snapshot mechanism and the store's behaviour
*)

let batch_1 = 48

(* not enough bakes to drag the savepoint after snapshot import *)
let batch_2 = 48

(* enough bakes to drag the savepoint *)
let batch_3 = 32

let blocks_per_cycle = 8

let preserved_cycles = 2

let retained_cycles = 8

let node_arguments = Node.[Synchronisation_threshold 0]

let get_head_max_op_ttl node =
  let* head = RPC.call node @@ RPC.get_chain_block_metadata () in
  return head.max_operations_ttl

(*

Tests both the snapshot mechanism and the store's behaviour

TL;DR, how it works:
- bake few blocks using all history modes
- export all kinds of snapshots
- import all kinds of snapshots
- check consistency (the snapshot's window includes genesis)
- bake a few blocks
- check consistency (the checkpoints should not move yet)
- bake a few blocks
- check consistency (the checkpoints should have moved)
- export all kinds of snapshots
- import all kinds of snapshots
- check consistency (checkpoints should be still valid)
- bake a few blocks
- check consistency (the checkpoints should have moved)

*)
let test_storage_snapshot =
  Protocol.register_test
    ~__FILE__
    ~title:"storage snapshot, import and export"
    ~tags:["storage"; "snapshot"; "import"; "export"]
  @@ fun protocol ->
  let show_node_group ns =
    sf "[%s]" (String.concat ", " @@ List.map (fun n -> Node.name n) ns)
  in

  let bake_batch batch_idx baker_node baker_client node_group batch_size =
    Log.info
      "Bake batch %d using %s (%d blocks)"
      batch_idx
      (Node.name baker_node)
      batch_size ;

    let level_before = Node.get_level baker_node in
    let final_level = level_before + batch_size in

    let group_level_promise =
      List.map
        (fun node -> (node, Node.wait_for_level node final_level))
        node_group
    in

    let* () =
      repeat batch_size @@ fun () ->
      let* () =
        Client.bake_for_and_wait
          ~endpoint:(Node baker_node)
          ~node:baker_node
          ~minimal_timestamp:true
          baker_client
      in
      unit
    in

    Log.info
      "Waiting for nodes %s to catch up to level %d"
      (show_node_group node_group)
      final_level ;
    let* () =
      Lwt_list.iter_p
        (fun (node, p) ->
          let* (_ : int) = p in
          Log.info "%s catched up" (Node.name node) ;
          unit)
        group_level_promise
    in
    Log.info "All catched up!" ;

    return final_level
  in

  let snapshot_dir = Temp.dir "multinode_snapshots" in

  let export node history_mode ~export_level ~snapshot =
    let file = snapshot_dir // snapshot in
    let* () = Node.snapshot_export ~export_level ~history_mode node file in
    Log.info "Node %s exported %s" (Node.name node) file ;
    unit
  in

  let create_or_reset mode ~snapshot =
    let snapshot = snapshot_dir // snapshot in
    let* node_name =
      match mode with
      | `New name -> return ("node_" ^ name)
      | `Reset old_node ->
          let* () = Node.terminate old_node in
          return (Node.name old_node ^ "'")
    in
    let* node =
      Node.init ~name:node_name ~snapshot:(snapshot, false) node_arguments
    in
    Log.info
      "%s node %s and imported %s"
      (match mode with `New _ -> "Created" | `Reset _ -> "Reset")
      (Node.name node)
      snapshot ;
    return node
  in

  let restart node =
    let* () = Node.terminate node in
    let* () = Node.run node node_arguments in
    let* () = Node.wait_for_ready node in
    unit
  in

  (* Checks that the node's checkpoint, savepoint and caboose are the *)
  (* expected ones after a snapshot import. *)
  let node_consistency_after_import node ~expected_level ~expected_checkpoint
      ~expected_savepoint ~expected_caboose =
    let* block_head = RPC.call node @@ RPC.get_chain_block () in
    let level = JSON.(block_head |-> "header" |-> "level" |> as_int) in

    let* {level = checkpoint; _} =
      RPC.call node @@ RPC.get_chain_level_checkpoint ()
    in
    let* {level = savepoint; _} =
      RPC.call node @@ RPC.get_chain_level_savepoint ()
    in
    let* {level = caboose; _} =
      RPC.call node @@ RPC.get_chain_level_caboose ()
    in
    Check.((level = expected_level) int)
      ~error_msg:"expected level = %R, got %L" ;
    Check.((checkpoint = expected_checkpoint) int)
      ~error_msg:"expected checkpoint = %R, got %L" ;
    Check.((savepoint = expected_savepoint) int)
      ~error_msg:"expected savepoint = %R, got %L" ;
    Check.((caboose = expected_caboose) int)
      ~error_msg:"expected caboose = %R, got %L" ;
    (* Check that the metadata of genesis is available *)
    let* (_ : JSON.t) = RPC.call node @@ RPC.get_chain_block ~block:"0" () in
    unit
  in

  let iter_block_range_s a b f =
    Lwt_list.iter_s f (range a b |> List.rev |> List.map string_of_int)
  in

  (* Checks the availability of blocks and its metadata for a full node. *)
  (* We assume that: *)
  (* - genesis is available with metadata *)
  (* - all block headers are available, *)
  (* - blocks before the savepoint (excluded) have pruned metadata, *)
  (* - blocks from the savepoint (included) have metadata. *)
  let full_node_blocks_availability node ~savepoint ~head_level =
    (* The metadata of genesis is available *)
    let* (_ : JSON.t) = RPC.call node @@ RPC.get_chain_block ~block:"0" () in
    let* () =
      iter_block_range_s 1 (savepoint - 1) @@ fun block ->
      let* (_ : JSON.t) =
        RPC.call node @@ RPC.get_chain_block_header ~block ()
      in
      (* Expect failure *)
      let* {body; code} =
        RPC.call_json node @@ RPC.get_chain_block_metadata ~block ()
      in
      (* In the client, attempting to retrieve missing metadata outputs:

         Command failed: Unable to find block
      *)
      Check.(
        (code = 500) ~__LOC__ int ~error_msg:"Expected HTTP status %R, got %L.") ;
      let error_id = JSON.(body |=> 0 |-> "id" |> as_string) in
      Check.(
        (error_id = "store.metadata_not_found")
          ~__LOC__
          string
          ~error_msg:"Expected error id %R, got %L") ;
      unit
    in
    iter_block_range_s savepoint head_level @@ fun block ->
    let* (_ : RPC.block_metadata) =
      RPC.call node @@ RPC.get_chain_block_metadata ~block ()
    in
    unit
  in

  (*  Checks the availability of blocks and its metadata for a rolling node. *)
  (*  We assume that: *)
  (*  - genesis is available with metadata *)
  (*  - blocks before caboose (excluded) are unknown, *)
  (*  - blocks from the caboose (included) and before the savepoint (excluded) *)
  (*    have pruned metadata, *)
  (*  - blocks after the savepoint (included) have metadata. *)
  let rolling_node_blocks_availability node ~savepoint ~caboose ~head_level =
    (* the metadata of genesis is available *)
    let* (_ : JSON.t) = RPC.call node @@ RPC.get_chain_block ~block:"0" () in
    let* () =
      if caboose <> 0 then (
        iter_block_range_s 1 (caboose - 1) @@ fun block ->
        let* {code; _} = RPC.call_raw node @@ RPC.get_chain_block ~block () in
        (* In the client, attempting to retrieve an unknown block outputs:

             Did not find service
        *)
        Check.(
          (code = 404)
            ~__LOC__
            int
            ~error_msg:"Expected HTTP status %R, got %L.") ;
        unit)
      else unit
    in
    iter_block_range_s (savepoint + 1) head_level @@ fun block ->
    let* (_ : JSON.t) = RPC.call node @@ RPC.get_chain_block_header ~block () in
    unit
  in

  let node_archive =
    Node.create
      ~name:"node_archive"
      (node_arguments @ Node.[History_mode Archive])
  in
  let node_full =
    Node.create
      ~name:"node_full"
      (node_arguments @ Node.[History_mode (Full None)])
  in
  let node_rolling =
    Node.create
      ~name:"node_rolling"
      (node_arguments @ Node.[History_mode (Rolling None)])
  in
  let nodes_cluster1 = [node_archive; node_full; node_rolling] in
  Cluster.clique nodes_cluster1 ;
  let* () = Cluster.start ~public:true nodes_cluster1 in
  let* client = Client.init () in

  let nodes_group1 = [node_archive; node_full; node_rolling] in

  let* () =
    Client.activate_protocol_and_wait
      ~endpoint:(Node node_archive)
      ~protocol
      ~node:node_archive
      client
  in

  (* Bake a few blocks *)
  let* snapshot_level = bake_batch 1 node_archive client nodes_group1 batch_1 in

  (* ########################################################################### *)
  Log.info "Export all kinds of snapshots" ;
  let* () =
    export
      node_archive
      Full_history
      ~export_level:snapshot_level
      ~snapshot:"node_archive_batch_1.full"
  and* () =
    export
      node_archive
      Rolling_history
      ~export_level:snapshot_level
      ~snapshot:"node_archive_batch_1.rolling"
  and* () =
    export
      node_full
      Full_history
      ~export_level:snapshot_level
      ~snapshot:"node_full_batch_1.full"
  and* () =
    export
      node_full
      Rolling_history
      ~export_level:snapshot_level
      ~snapshot:"node_full_batch_1.rolling"
  and* () =
    export
      node_rolling
      Rolling_history
      ~export_level:snapshot_level
      ~snapshot:"node_rolling_batch_1.rolling"
  in

  Log.info "Import all kinds of snapshots" ;

  let* node_full_2 =
    (* New node: 3 *)
    create_or_reset (`New "full_2") ~snapshot:"node_archive_batch_1.full"
  and* node_rolling_2 =
    (* New node: 4 *)
    create_or_reset (`New "rolling_2") ~snapshot:"node_archive_batch_1.rolling"
  and* node_full =
    (* Reset node 1 *)
    create_or_reset (`Reset node_full) ~snapshot:"node_full_batch_1.full"
  and* node_rolling_3 =
    (* New node: 5 *)
    create_or_reset (`New "rolling_3") ~snapshot:"node_full_batch_1.rolling"
  and* node_rolling =
    (* Reset node 2 *)
    create_or_reset
      (`Reset node_rolling)
      ~snapshot:"node_rolling_batch_1.rolling"
  in
  let nodes_group2 =
    [
      node_archive;
      node_full;
      node_rolling;
      node_full_2;
      node_rolling_2;
      node_rolling_3;
    ]
  in

  let connect_clique =
    Cluster.meta_clique_lwt @@ fun peer peer' ->
    Log.debug "Connecting %s to %s" (Node.name peer) (Node.name peer') ;
    Client.Admin.connect_address ~endpoint:(Node peer) ~peer:peer' client
  in

  Log.info "Group 2: Connect nodes %s in clique" (show_node_group nodes_group2) ;
  let* () = connect_clique nodes_group2 in

  (* ########################################################################### *)
  Log.info "Check consistency 1" ;

  (*
    Check consistency of imported snapshots
    Do not factorize calls to ease debugging
   *)

  (* For the full nodes *)

  (* test_node_full_consistency_1 *)
  let* () =
    let node = node_full in
    Log.info "Check consistency (node_full, %s)." (Node.name node) ;
    let* () = restart node in
    let expected_level = snapshot_level in
    let expected_checkpoint = expected_level in
    let expected_savepoint = expected_checkpoint in
    let expected_caboose = 0 in

    let* () =
      node_consistency_after_import
        node
        ~expected_level
        ~expected_checkpoint
        ~expected_savepoint
        ~expected_caboose
    in
    let* () =
      full_node_blocks_availability
        node
        ~savepoint:expected_savepoint
        ~head_level:expected_level
    in
    unit
  in
  (* test_node_full_2_consistency_1 *)
  let* () =
    let node = node_full_2 in
    Log.info "Check consistency (node_full_2, %s)" (Node.name node) ;
    let* () = restart node in
    let expected_level = snapshot_level in
    let expected_checkpoint = expected_level in
    let expected_savepoint = expected_checkpoint in
    let expected_caboose = 0 in
    let* () =
      node_consistency_after_import
        node
        ~expected_level
        ~expected_checkpoint
        ~expected_savepoint
        ~expected_caboose
    in
    let* () =
      full_node_blocks_availability
        node
        ~savepoint:expected_savepoint
        ~head_level:expected_level
    in
    unit
  in
  (* For the rolling nodes *)
  (* test_node_rolling_consistency_1 *)
  let* () =
    let node = node_rolling in
    Log.info "Check consistency (node_rolling, %s)" (Node.name node) ;
    let* () = restart node in
    let expected_level = snapshot_level in
    let expected_checkpoint = expected_level in
    let expected_savepoint = expected_checkpoint in
    let expected_caboose = 0 in
    let* () =
      node_consistency_after_import
        node
        ~expected_level
        ~expected_checkpoint
        ~expected_savepoint
        ~expected_caboose
    in
    let* () =
      rolling_node_blocks_availability
        node
        ~savepoint:expected_savepoint
        ~caboose:expected_caboose
        ~head_level:expected_level
    in
    unit
  in
  (* test_node_rolling_2_consistency_1 *)
  let* () =
    let node = node_rolling_2 in
    Log.info "Check consistency (node_rolling_2, %s)" (Node.name node) ;
    let* () = restart node in
    let expected_level = snapshot_level in
    let expected_checkpoint = expected_level in
    let expected_savepoint = expected_checkpoint in
    let expected_caboose = 0 in
    let* () =
      node_consistency_after_import
        node
        ~expected_level
        ~expected_checkpoint
        ~expected_savepoint
        ~expected_caboose
    in
    let* () =
      rolling_node_blocks_availability
        node
        ~savepoint:expected_savepoint
        ~caboose:expected_caboose
        ~head_level:expected_level
    in
    unit
  in
  (* test_node_rolling_3_consistency_1 *)
  let* () =
    let node = node_rolling_3 in
    Log.info "Check consistency (node_rolling_3, %s)" (Node.name node) ;
    let* () = restart node in
    let expected_level = snapshot_level in
    let expected_checkpoint = expected_level in
    let expected_savepoint = expected_checkpoint in
    let expected_caboose = 0 in
    let* () =
      node_consistency_after_import
        node
        ~expected_level
        ~expected_checkpoint
        ~expected_savepoint
        ~expected_caboose
    in
    let* () =
      rolling_node_blocks_availability
        node
        ~savepoint:expected_savepoint
        ~caboose:expected_caboose
        ~head_level:expected_level
    in
    unit
  in

  (* ########################################################################### *)
  (* Bake a few blocks *)
  let* head_level = bake_batch 2 node_archive client nodes_group2 batch_2 in

  (* ########################################################################### *)
  Log.info "Check consistency 2: imported snapshots after > 5 baked cycles" ;
  Log.info
    "The savepoints of full and rolling nodes **have not** been dragged yet" ;

  (* For the full nodes *)

  (* test_node_full_consistency_2 *)
  let* () =
    let node = node_full in
    Log.info "Check consistency 2 (node_full, %s)." (Node.name node) ;
    let* () = restart node in
    let expected_level = head_level in
    let expected_checkpoint =
      expected_level - (preserved_cycles * blocks_per_cycle)
    in
    let savepoint_when_imported = snapshot_level in
    let expected_savepoint = savepoint_when_imported in
    let expected_caboose = 0 in

    let* () =
      node_consistency_after_import
        node
        ~expected_level
        ~expected_checkpoint
        ~expected_savepoint
        ~expected_caboose
    in
    let* () =
      full_node_blocks_availability
        node
        ~savepoint:expected_savepoint
        ~head_level:expected_level
    in
    unit
  in

  (* For the full nodes *)
  (* test_node_full_2_consistency_2 *)
  let* () =
    let node = node_full_2 in
    Log.info "Check consistency 2 (node_full_2, %s)." (Node.name node) ;
    let* () = restart node in
    let expected_level = head_level in
    (* last allowed fork level of the head *)
    let expected_checkpoint =
      expected_level - (preserved_cycles * blocks_per_cycle)
    in
    let savepoint_when_imported = snapshot_level in
    let expected_savepoint = savepoint_when_imported in
    let expected_caboose = 0 in

    let* () =
      node_consistency_after_import
        node
        ~expected_level
        ~expected_checkpoint
        ~expected_savepoint
        ~expected_caboose
    in
    let* () =
      full_node_blocks_availability
        node
        ~savepoint:expected_savepoint
        ~head_level:expected_level
    in
    unit
  in

  (* For the rolling nodes *)

  (* The caboose of rolling mode were no dragged yet as *)
  (* (checkpoint - max_op_ttl(head)) < savepoint *)

  (* test_node_rolling_consistency_2 *)
  let* () =
    let node = node_rolling in
    Log.info "Check consistency 2 (node_rolling, %s)." (Node.name node) ;
    let* () = restart node in
    let expected_level = head_level in
    (* last allowed fork level of the head *)
    let expected_checkpoint =
      expected_level - (preserved_cycles * blocks_per_cycle)
    in
    let savepoint_when_imported = snapshot_level in
    let expected_savepoint = savepoint_when_imported in
    let* expected_caboose =
      let* max_op_ttl = get_head_max_op_ttl node in
      return @@ max (expected_checkpoint - max_op_ttl) 0
    in
    let* () =
      node_consistency_after_import
        node
        ~expected_level
        ~expected_checkpoint
        ~expected_savepoint
        ~expected_caboose
    in
    let* () =
      rolling_node_blocks_availability
        node
        ~savepoint:expected_savepoint
        ~caboose:expected_caboose
        ~head_level:expected_level
    in
    unit
  in

  (* test_node_rolling_2_consistency_2 *)
  let* () =
    let node = node_rolling_2 in
    Log.info "Check consistency 2 (node_rolling_2, %s)." (Node.name node) ;
    let* () = restart node in
    let expected_level = head_level in
    (* last allowed fork level of the head *)
    let expected_checkpoint =
      expected_level - (preserved_cycles * blocks_per_cycle)
    in
    let savepoint_when_imported = snapshot_level in
    let expected_savepoint = savepoint_when_imported in
    let* expected_caboose =
      let* max_op_ttl = get_head_max_op_ttl node in
      return @@ max (expected_checkpoint - max_op_ttl) 0
    in
    let* () =
      node_consistency_after_import
        node
        ~expected_level
        ~expected_checkpoint
        ~expected_savepoint
        ~expected_caboose
    in
    let* () =
      rolling_node_blocks_availability
        node
        ~savepoint:expected_savepoint
        ~caboose:expected_caboose
        ~head_level:expected_level
    in
    unit
  in

  (* test_node_rolling_3_consistency_2 *)
  let* () =
    let node = node_rolling_3 in
    Log.info "Check consistency 2 (node_rolling_3, %s)." (Node.name node) ;
    let* () = restart node in
    let expected_level = head_level in
    (* last allowed fork level of the head *)
    let expected_checkpoint =
      expected_level - (preserved_cycles * blocks_per_cycle)
    in
    let savepoint_when_imported = snapshot_level in
    let expected_savepoint = savepoint_when_imported in
    let* expected_caboose =
      let* max_op_ttl = get_head_max_op_ttl node in
      return @@ max (expected_checkpoint - max_op_ttl) 0
    in
    let* () =
      node_consistency_after_import
        node
        ~expected_level
        ~expected_checkpoint
        ~expected_savepoint
        ~expected_caboose
    in
    let* () =
      rolling_node_blocks_availability
        node
        ~savepoint:expected_savepoint
        ~caboose:expected_caboose
        ~head_level:expected_level
    in
    unit
  in

  (* ########################################################################### *)
  (* Bake a few blocks *)
  let* head_level = bake_batch 3 node_archive client nodes_group2 batch_3 in
  let snapshot_level = head_level in

  (* ########################################################################### *)
  Log.info "Check consistency 3: imported snapshots after > 5 baked cycles" ;

  (* The savepoints of full and rolling nodes **have** been dragged *)

  (* For the full nodes *)

  (* test_node_full_consistency_3 *)
  let* () =
    let node = node_full in
    Log.info "Check consistency 3 (node_full, %s)." (Node.name node) ;
    let* () = restart node in
    let expected_level = head_level in
    (* last allowed fork level of the head *)
    let expected_checkpoint =
      expected_level - (preserved_cycles * blocks_per_cycle)
    in
    let expected_savepoint =
      expected_checkpoint - (retained_cycles * blocks_per_cycle)
    in
    let expected_caboose = 0 in

    let* () =
      node_consistency_after_import
        node
        ~expected_level
        ~expected_checkpoint
        ~expected_savepoint
        ~expected_caboose
    in
    let* () =
      full_node_blocks_availability
        node
        ~savepoint:expected_savepoint
        ~head_level:expected_level
    in
    unit
  in

  (* test_node_full_2_consistency_3 *)
  let* () =
    let node = node_full_2 in
    Log.info "Check consistency 3 (node_full_2, %s)." (Node.name node) ;
    let* () = restart node in
    let expected_level = head_level in
    (* last allowed fork level of the head *)
    let expected_checkpoint =
      expected_level - (preserved_cycles * blocks_per_cycle)
    in
    let expected_savepoint =
      expected_checkpoint - (retained_cycles * blocks_per_cycle)
    in
    let expected_caboose = 0 in

    let* () =
      node_consistency_after_import
        node
        ~expected_level
        ~expected_checkpoint
        ~expected_savepoint
        ~expected_caboose
    in
    let* () =
      full_node_blocks_availability
        node
        ~savepoint:expected_savepoint
        ~head_level:expected_level
    in
    unit
  in

  (* For the rolling nodes *)

  (* test_node_rolling_consistency_3 *)
  let* () =
    let node = node_rolling in
    Log.info "Check consistency 3 (node_rolling, %s)." (Node.name node) ;
    let* () = restart node in
    let expected_level = head_level in
    (* last allowed fork level of the head *)
    let expected_checkpoint =
      expected_level - (preserved_cycles * blocks_per_cycle)
    in
    let expected_savepoint =
      expected_checkpoint - (retained_cycles * blocks_per_cycle)
    in
    let* expected_caboose =
      let* max_op_ttl = get_head_max_op_ttl node in
      return @@ max (expected_checkpoint - max_op_ttl) 0
    in
    let* () =
      node_consistency_after_import
        node
        ~expected_level
        ~expected_checkpoint
        ~expected_savepoint
        ~expected_caboose
    in
    let* () =
      rolling_node_blocks_availability
        node
        ~savepoint:expected_savepoint
        ~caboose:expected_caboose
        ~head_level:expected_level
    in
    unit
  in

  (* test_node_rolling_2_consistency_3 *)
  let* () =
    let node = node_rolling_2 in
    Log.info "Check consistency 3 (node_rolling_2, %s)." (Node.name node) ;
    let* () = restart node in
    let expected_level = head_level in
    (* last allowed fork level of the head *)
    let expected_checkpoint =
      expected_level - (preserved_cycles * blocks_per_cycle)
    in
    let expected_savepoint =
      expected_checkpoint - (retained_cycles * blocks_per_cycle)
    in
    let* expected_caboose =
      let* max_op_ttl = get_head_max_op_ttl node in
      return @@ max (expected_checkpoint - max_op_ttl) 0
    in
    let* () =
      node_consistency_after_import
        node
        ~expected_level
        ~expected_checkpoint
        ~expected_savepoint
        ~expected_caboose
    in
    let* () =
      rolling_node_blocks_availability
        node
        ~savepoint:expected_savepoint
        ~caboose:expected_caboose
        ~head_level:expected_level
    in
    unit
  in

  (* test_node_rolling_3_consistency_3 *)
  let* () =
    let node = node_rolling_3 in
    Log.info "Check consistency 3 (node_rolling_3, %s)." (Node.name node) ;
    let* () = restart node in
    let expected_level = head_level in
    (* last allowed fork level of the head *)
    let expected_checkpoint =
      expected_level - (preserved_cycles * blocks_per_cycle)
    in
    let expected_savepoint =
      expected_checkpoint - (retained_cycles * blocks_per_cycle)
    in
    let* expected_caboose =
      let* max_op_ttl = get_head_max_op_ttl node in
      return @@ max (expected_checkpoint - max_op_ttl) 0
    in
    let* () =
      node_consistency_after_import
        node
        ~expected_level
        ~expected_checkpoint
        ~expected_savepoint
        ~expected_caboose
    in
    let* () =
      rolling_node_blocks_availability
        node
        ~savepoint:expected_savepoint
        ~caboose:expected_caboose
        ~head_level:expected_level
    in
    unit
  in

  (* ########################################################################### *)
  Log.info "Re-export all kinds of snapshots" ;
  let* () =
    export
      node_archive
      Full_history
      ~export_level:snapshot_level
      ~snapshot:"node_archive_batch_3.full"
  and* () =
    export
      node_archive
      Rolling_history
      ~export_level:snapshot_level
      ~snapshot:"node_archive_batch_3.rolling"
  and* () =
    export
      node_full
      Full_history
      ~export_level:snapshot_level
      ~snapshot:"node_full_batch_3.full"
  and* () =
    export
      node_full
      Rolling_history
      ~export_level:snapshot_level
      ~snapshot:"node_full_batch_3.rolling"
  and* () =
    export
      node_rolling
      Rolling_history
      ~export_level:snapshot_level
      ~snapshot:"node_rolling_batch_3.rolling"
  in

  (* ########################################################################### *)
  Log.info "Re-import all kinds of snapshots" ;

  let* node_full_2 =
    (* Reset node: 3 *)
    create_or_reset (`Reset node_full_2) ~snapshot:"node_archive_batch_3.full"
  and* node_rolling_2 =
    (* Reset node: 4 *)
    create_or_reset
      (`Reset node_rolling_2)
      ~snapshot:"node_archive_batch_3.rolling"
  and* node_full =
    (* Reset node 1 *)
    create_or_reset (`Reset node_full) ~snapshot:"node_full_batch_3.full"
  and* node_rolling_3 =
    (* New node: 5 *)
    create_or_reset
      (`Reset node_rolling_3)
      ~snapshot:"node_full_batch_3.rolling"
  and* node_rolling =
    (* Reset node 2 *)
    create_or_reset
      (`Reset node_rolling)
      ~snapshot:"node_rolling_batch_3.rolling"
  in

  (* ########################################################################### *)
  Log.info "Check consistency 4: imported snapshots with > 5 cycles" ;

  (* For the full nodes *)

  (* test_node_full_consistency_4 *)
  let* () =
    let node = node_full in
    Log.info "Check consistency 4 (node_full, %s)." (Node.name node) ;
    let* () = restart node in
    let expected_level = head_level in
    (* last allowed fork level of the head *)
    let expected_checkpoint = expected_level in
    let expected_savepoint = expected_checkpoint in
    let expected_caboose = 0 in

    let* () =
      node_consistency_after_import
        node
        ~expected_level
        ~expected_checkpoint
        ~expected_savepoint
        ~expected_caboose
    in
    let* () =
      full_node_blocks_availability
        node
        ~savepoint:expected_savepoint
        ~head_level:expected_level
    in
    unit
  in

  (* test_node_full_2_consistency_4 *)
  let* () =
    let node = node_full_2 in
    Log.info "Check consistency 4 (node_full_2, %s)." (Node.name node) ;
    let* () = restart node in
    let expected_level = head_level in
    (* last allowed fork level of the head *)
    let expected_checkpoint = expected_level in
    let expected_savepoint = expected_checkpoint in
    let expected_caboose = 0 in

    let* () =
      node_consistency_after_import
        node
        ~expected_level
        ~expected_checkpoint
        ~expected_savepoint
        ~expected_caboose
    in
    let* () =
      full_node_blocks_availability
        node
        ~savepoint:expected_savepoint
        ~head_level:expected_level
    in
    unit
  in

  (* For the rolling nodes *)

  (* test_node_rolling_consistency_4 *)
  let* () =
    let node = node_rolling in
    Log.info "Check consistency 4 (node_rolling, %s)." (Node.name node) ;
    let* () = restart node in
    let expected_level = head_level in
    let expected_checkpoint = expected_level in
    let expected_savepoint = expected_checkpoint in
    let* expected_caboose =
      let* max_op_ttl = get_head_max_op_ttl node in
      return @@ (expected_checkpoint - max_op_ttl)
    in
    let* () =
      node_consistency_after_import
        node
        ~expected_level
        ~expected_checkpoint
        ~expected_savepoint
        ~expected_caboose
    in
    let* () =
      rolling_node_blocks_availability
        node
        ~savepoint:expected_savepoint
        ~caboose:expected_caboose
        ~head_level:expected_level
    in
    unit
  in

  (* test_node_rolling_2_consistency_4 *)
  let* () =
    let node = node_rolling_2 in
    Log.info "Check consistency 4 (node_rolling_2, %s)." (Node.name node) ;
    let* () = restart node in
    let expected_level = head_level in
    let expected_checkpoint = expected_level in
    let expected_savepoint = expected_checkpoint in
    let* expected_caboose =
      let* max_op_ttl = get_head_max_op_ttl node in
      return @@ (expected_checkpoint - max_op_ttl)
    in
    let* () =
      node_consistency_after_import
        node
        ~expected_level
        ~expected_checkpoint
        ~expected_savepoint
        ~expected_caboose
    in
    let* () =
      rolling_node_blocks_availability
        node
        ~savepoint:expected_savepoint
        ~caboose:expected_caboose
        ~head_level:expected_level
    in
    unit
  in

  (* test_node_rolling_3_consistency_4 *)
  let* () =
    let node = node_rolling_3 in
    Log.info "Check consistency 4 (node_rolling_3, %s)." (Node.name node) ;
    let* () = restart node in
    let expected_level = head_level in
    let expected_checkpoint = expected_level in
    let expected_savepoint = expected_checkpoint in
    let* expected_caboose =
      let* max_op_ttl = get_head_max_op_ttl node in
      return @@ (expected_checkpoint - max_op_ttl)
    in
    let* () =
      node_consistency_after_import
        node
        ~expected_level
        ~expected_checkpoint
        ~expected_savepoint
        ~expected_caboose
    in
    let* () =
      rolling_node_blocks_availability
        node
        ~savepoint:expected_savepoint
        ~caboose:expected_caboose
        ~head_level:expected_level
    in
    unit
  in

  unit

let register ~protocols = test_storage_snapshot protocols
back to top