https://gitlab.com/tezos/tezos
Raw File
Tip revision: a6b06d7bcde6471e2982e9881788812c9bd07995 authored by François Thiré on 16 February 2024, 10:52:23 UTC
Tezt/DAL: Change sandboxed parameters
Tip revision: a6b06d7
yes_wallet.ml
(*****************************************************************************)
(*                                                                           *)
(* Open Source License                                                       *)
(* Copyright (c) 2018 Dynamic Ledger Solutions, Inc. <contact@tezos.com>     *)
(* 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.                                                 *)
(*                                                                           *)
(*****************************************************************************)

type error = Overwrite_forbiden of string | File_not_found of string

(* We need to exit Lwt + tzResult context from Yes_wallet. *)
let run_load_bakers_public_keys ?staking_share_opt ?network_opt ?level base_dir
    ~active_bakers_only alias_pkh_pk_list =
  let open Yes_wallet_lib in
  let open Tezos_error_monad in
  match
    Lwt_main.run
      (load_bakers_public_keys
         ?staking_share_opt
         ?network_opt
         ?level
         base_dir
         ~active_bakers_only
         alias_pkh_pk_list)
  with
  | Ok alias_pkh_pk_list -> alias_pkh_pk_list
  | Error trace ->
      Format.eprintf "error:@.%a@." Error_monad.pp_print_trace trace ;
      exit 1

let run_load_contracts ?dump_contracts ?network_opt ?level base_dir =
  let open Yes_wallet_lib in
  let open Tezos_error_monad in
  match
    Lwt_main.run (load_contracts ?dump_contracts ?network_opt ?level base_dir)
  with
  | Ok l -> l
  | Error trace ->
      Format.eprintf "error:@.%a@." Error_monad.pp_print_trace trace ;
      exit 1

let run_build_yes_wallet ?staking_share_opt ?network_opt base_dir
    ~active_bakers_only ~aliases =
  let open Yes_wallet_lib in
  let open Tezos_error_monad in
  match
    Lwt_main.run
      (build_yes_wallet
         ?staking_share_opt
         ?network_opt
         base_dir
         ~active_bakers_only
         ~aliases)
  with
  | Ok alias_pkh_pk_list -> alias_pkh_pk_list
  | Error trace ->
      Format.eprintf "error:@.%a@." Error_monad.pp_print_trace trace ;
      exit 1

let try_copy ~replace source target =
  if source = target then Ok ()
  else
    let tmp_target = Filename.temp_file target ".tmp" in
    if Sys.file_exists source then
      if (not (Sys.file_exists target)) || replace then (
        let chin = open_in source
        and chout = open_out (if replace then tmp_target else target) in
        (try
           while true do
             let input = input_line chin in
             output_string chout input
           done
         with End_of_file -> ()) ;
        close_in chin ;
        close_out chout ;
        Ok (Sys.rename tmp_target target))
      else Error (Overwrite_forbiden target)
    else Error (File_not_found source)

let convert_wallet ~replace wallet_dir target_dir =
  let pkh_filename = "public_key_hashs" in
  let pk_filename = "public_keys" in
  let sk_filename = "secret_keys" in
  if not (Sys.file_exists wallet_dir) then
    failwith "wallet does not exists, cannot convert empty wallet."
  else
    let pkh_target = Filename.concat target_dir pkh_filename in
    let pk_target = Filename.concat target_dir pk_filename in
    let sk_target = Filename.concat target_dir sk_filename in
    let pkh_source = Filename.concat wallet_dir pkh_filename in
    let pk_source = Filename.concat wallet_dir pk_filename in
    if
      (Sys.file_exists pkh_target || Sys.file_exists pk_target
     || Sys.file_exists sk_target)
      && not replace
    then
      Format.eprintf
        "Warning: cannot write yes-wallet, at least one of the following files \
         already exists: %s/{%s,%s,%s} (you can use --force  option to \
         overwrite files)@."
        target_dir
        pkh_filename
        pk_filename
        sk_filename
    else if Sys.file_exists pk_source then (
      let sk_list = sk_list_of_pk_file pk_source in
      json_to_file sk_list sk_target ;
      (match try_copy ~replace pk_source pk_target with
      | Ok () -> ()
      | Error (Overwrite_forbiden _) ->
          assert false (* [replace] is [true] in this branch *)
      | Error (File_not_found _) ->
          assert false (* [pk_source] file exists int this branch *)) ;
      match try_copy ~replace pkh_source pkh_target with
      | Ok () -> ()
      | Error (Overwrite_forbiden _) ->
          assert false (* replace is true in this branch *)
      | Error (File_not_found _) -> ())
    else
      Format.eprintf
        "Warning: cannot produce yes-wallet, the file %s does not exist@."
        pk_source

let populate_wallet ~replace yes_wallet_dir alias_pkh_pk_list =
  let pkh_filename = Filename.concat yes_wallet_dir "public_key_hashs" in
  let pk_filename = Filename.concat yes_wallet_dir "public_keys" in
  let sk_filename = Filename.concat yes_wallet_dir "secret_keys" in
  if not (Sys.file_exists yes_wallet_dir) then Unix.mkdir yes_wallet_dir 0o750 ;
  if
    (Sys.file_exists pkh_filename
    || Sys.file_exists pk_filename
    || Sys.file_exists sk_filename)
    && not replace
  then (
    Format.eprintf
      "Warning: cannot write wallet, at least one of the following files \
       already exists: %s/{%s,%s,%s} (you can use --force  option to overwrite \
       files)@."
      yes_wallet_dir
      pkh_filename
      pk_filename
      sk_filename ;
    false)
  else (
    Yes_wallet_lib.write_yes_wallet yes_wallet_dir alias_pkh_pk_list ;
    true)

let alias_file_opt_name = "--aliases"

let alias_file_extension = ".json"

let active_bakers_only_opt_name = "--active-bakers-only"

let force_opt_name = "--force"

let staking_share_opt_name = "--staking-share"

let network_opt_name = "--network"

let level_opt_name = "--level"

let supported_network =
  List.map fst Octez_node_config.Config_file.builtin_blockchain_networks

let force = ref false

let confirm_rewrite wallet =
  Format.printf
    "/!\\ Warning /!\\: You are about to rewrite all secret keys from wallet \
     %s /!\\\n\n\
     All SECRET KEYS of the wallet will be LOST.\n\n\
     Are you sure you want to do that ? press Y or y to proceed, any other key \
     to cancel.@."
    wallet ;
  String.uppercase_ascii (read_line ()) = "Y"

let usage () =
  Format.printf
    "@[<v>@[<v 4>> convert wallet <source-wallet-dir> in <yes-wallet-dir>@,\
     creates a yes-wallet in <yes-wallet-dir> with the public keys from \
     <source-wallet-dir>@]@,\
     @[<v>@[<v 4>> convert wallet <wallet-dir> inplace@,\
     same as above but overwrite the file in the directory <wallet-dir>@]@,\
     @[<v 4>> create from context <base_dir> in <yes_wallet_dir> [%s] [%s \
     <NUM>] [%s <%a>]@,\
     creates a yes-wallet with all delegates in the head block of the context \
     in <base_dir> and store it in <yes_wallet_dir>@,\
     if %s is used the deactivated bakers are filtered out@,\
     if %s <NUM> is used, the first largest bakers that have an accumulated \
     stake of at least <NUM> percent of the total stake are kept@,\
     if %s <%a> is used the store is opened using the right genesis parameter \
     (default is mainnet) @]@]@,\
     @[<v>@[<v 4>> compute total supply from <base_dir> [in <csv_file>]@,\
     computes the total supply form all contracts and commitments. result is \
     printed in stantdard output, optionally informations on all read \
     contracts can be dumped into csv_file@]@]@,\
     @[<v 4>> dump staking balances from <base_dir> in <csv_file>]@,\
     saves the staking balances of all delegates in the target csv file@,\
     @[<v>if %s <FILE> is used, it will input aliases from an .json file.See \
     README.md for the spec of this file and how to generate it.@],@[<v>if %s \
     is used existing files will be overwritten@]@."
    active_bakers_only_opt_name
    staking_share_opt_name
    network_opt_name
    Format.(
      pp_print_list
        ~pp_sep:(fun ppf () -> pp_print_string ppf "|")
        pp_print_string)
    supported_network
    active_bakers_only_opt_name
    staking_share_opt_name
    network_opt_name
    Format.(
      pp_print_list
        ~pp_sep:(fun ppf () -> pp_print_string ppf "|")
        pp_print_string)
    supported_network
    alias_file_opt_name
    force_opt_name

let () =
  let argv = Array.to_list Sys.argv in
  let staking_share_opt =
    let rec aux argv =
      match argv with
      | [] -> None
      | str :: percentage :: _ when str = staking_share_opt_name ->
          let percentage = Int64.of_string percentage in
          assert (0L < percentage && percentage <= 100L) ;
          Some percentage
      | _ :: argv' -> aux argv'
    in
    aux argv
  in
  let network_opt =
    let rec aux argv =
      match argv with
      | [] -> None
      | str :: net :: _ when str = network_opt_name -> Some net
      | _ :: argv' -> aux argv'
    in
    aux argv
  in
  let level_opt =
    let rec aux argv =
      match argv with
      | [] -> None
      | str :: level :: _ when str = level_opt_name ->
          let level = Int32.of_string level in
          Some level
      | _ :: argv' -> aux argv'
    in
    aux argv
  in

  (* Take an alias file as input. *)
  let alias_file_opt =
    let rec aux argv =
      match argv with
      | [] -> None
      | str :: file :: _ when str = alias_file_opt_name ->
          if Sys.file_exists file then Some file
          else (
            Format.eprintf
              "Warning: %a doesn't point to an existing alias file.\n"
              Format.pp_print_string
              file ;
            None)
      | _ :: argv' -> aux argv'
    in
    aux argv
  in
  let options, argv =
    List.partition
      (fun arg ->
        (String.length arg > 0 && String.get arg 0 = '-')
        || Str.string_match (Str.regexp "[0-9]+") arg 0
        (* FIME this is an uggly hack, but hey -lets' force alias files
           to have a .json extension.*)
        || String.ends_with ~suffix:alias_file_extension arg
        || List.mem (String.lowercase_ascii arg) supported_network)
      argv
  in
  let active_bakers_only =
    List.exists (fun opt -> opt = active_bakers_only_opt_name) options
  in
  force := List.exists (fun opt -> opt = force_opt_name) options ;
  let unknown_options =
    let rec filter args =
      match args with
      | [] -> []
      | opt :: t when opt = active_bakers_only_opt_name -> filter t
      | opt :: t when opt = force_opt_name -> filter t
      | opt :: num :: t
        when opt = staking_share_opt_name
             && Str.string_match (Str.regexp "[0-9]+") num 0 ->
          filter t
      | opt :: num :: t
        when opt = level_opt_name
             && Str.string_match (Str.regexp "[0-9]+") num 0 ->
          filter t
      | opt :: file :: t
        when opt = alias_file_opt_name
             && String.ends_with ~suffix:alias_file_extension file ->
          filter t
      | opt :: net :: t
        when opt = network_opt_name
             && List.mem (String.lowercase_ascii net) supported_network ->
          filter t
      | h :: t -> h :: filter t
    in
    filter options
  in
  (* load alias file *)
  let aliases =
    match alias_file_opt with
    | None -> []
    | Some file -> Yes_wallet_lib.load_alias_file file
  in
  if unknown_options <> [] then
    Format.eprintf
      "Warning: unknown options %a@."
      (Format.pp_print_list Format.pp_print_string)
      unknown_options ;
  match argv with
  | [] -> assert false
  | [_] ->
      usage () ;
      exit 0
  | [_; "create"; "from"; "context"; base_dir; "in"; yes_wallet_dir] ->
      if not (Sys.file_exists base_dir) then (
        Format.eprintf "Invalid --data-dir provided: %s.\n" base_dir ;
        exit 1)
      else
        let yes_alias_list =
          run_build_yes_wallet
            ~staking_share_opt
            ?network_opt
            base_dir
            ~active_bakers_only
            ~aliases
        in
        Format.printf
          "@[<h>Number of keys to export:@;<3 0>%d@]@."
          (List.length yes_alias_list) ;
        if populate_wallet ~replace:!force yes_wallet_dir yes_alias_list then
          Format.printf "@[<h>Exported path:@;<14 0>%s@]@." yes_wallet_dir
  | [_; "convert"; "wallet"; base_dir; "in"; target_dir] ->
      convert_wallet ~replace:!force base_dir target_dir ;
      Format.printf
        "Wallet %s converted to a yes-wallet in %s@."
        base_dir
        target_dir
  | [_; "convert"; "wallet"; base_dir; "inplace"] ->
      if !force || confirm_rewrite base_dir then (
        convert_wallet ~replace:true base_dir base_dir ;
        Format.printf "Converted wallet in %s@." base_dir)
      else
        Format.printf
          "I refuse to rewrite files in %s without confirmation or --force \
           flag@."
          base_dir
  | _ :: "compute" :: "total" :: "supply" :: "from" :: base_dir :: tl -> (
      let dump_contracts =
        match tl with ["in"; csv_file] -> Some csv_file | _ -> None
      in

      let contracts_list =
        run_load_contracts ~dump_contracts ?level:level_opt base_dir
      in

      match dump_contracts with
      | Some csv_file ->
          let flags =
            if !force then [Open_wronly; Open_creat; Open_trunc; Open_text]
            else [Open_wronly; Open_creat; Open_excl; Open_text]
          in

          Out_channel.with_open_gen flags 0o666 csv_file (fun oc ->
              let fmtr = Format.formatter_of_out_channel oc in

              Format.fprintf
                fmtr
                "address, balance, frozen_bonds, staked_balance, \
                 unstaked_frozen_balance, unstaked_finalizable_balance, @." ;
              List.iter
                (fun {
                       address;
                       balance;
                       frozen_bonds;
                       staked_balance;
                       unstaked_frozen_balance;
                       unstaked_finalizable_balance;
                     } ->
                  Format.fprintf
                    fmtr
                    "%s, %Ld, %Ld, %Ld, %Ld, %Ld@."
                    address
                    balance
                    frozen_bonds
                    staked_balance
                    unstaked_frozen_balance
                    unstaked_finalizable_balance)
                contracts_list)
      | None -> exit 0)
  | [_; "dump"; "staking"; "balances"; "from"; base_dir; "in"; csv_file] ->
      let alias_pkh_pk_list =
        run_load_bakers_public_keys
          ~staking_share_opt
          ?network_opt
          ?level:level_opt
          base_dir
          ~active_bakers_only
          aliases
      in

      let flags =
        if !force then [Open_wronly; Open_creat; Open_trunc; Open_text]
        else [Open_wronly; Open_creat; Open_excl; Open_text]
      in
      Out_channel.with_open_gen flags 0o666 csv_file (fun oc ->
          let fmtr = Format.formatter_of_out_channel oc in

          Format.fprintf
            fmtr
            "pkh, stake, spendable_balance, frozen_deposits, \
             unstake_frozen_deposits\n" ;
          List.iter
            (fun ( _alias,
                   pkh,
                   _pk,
                   stake,
                   frozen_deposits,
                   unstake_frozen_deposits ) ->
              Format.fprintf
                fmtr
                "%s, %Ld, %Ld, %Ld\n"
                pkh
                stake
                frozen_deposits
                unstake_frozen_deposits)
            alias_pkh_pk_list)
  | _ ->
      Format.eprintf "Invalid command. Usage:@." ;
      usage () ;
      exit 1
back to top