(**************************************************************************) (* *) (* Copyright (c) 2014 - 2017. *) (* Dynamic Ledger Solutions, Inc. *) (* *) (* All rights reserved. No warranty, explicit or implicit, provided. *) (* *) (**************************************************************************) open Logging.Node.Main let genesis : State.Net.genesis = { time = Time.of_notation_exn "2017-10-19T00:00:00Z" ; block = Block_hash.of_b58check_exn "BLockGenesisGenesisGenesisGenesisGenesisFFFFFgtaC8G" ; protocol = Protocol_hash.of_b58check_exn "ProtoGenesisGenesisGenesisGenesisGenesisGenesk612im" ; } type error += Non_private_sandbox of P2p_types.addr let () = register_error_kind `Permanent ~id:"main.run.non_private_sandbox" ~title:"Forbidden public sandbox" ~description:"A sandboxed node should not listen on a public address." ~pp:begin fun ppf addr -> Format.fprintf ppf "The node is configured to listen on a public address (%a), \ while only 'private' networks are authorised with `--sandbox`. See `%s run --help` on how to change the listening address." Ipaddr.V6.pp_hum addr Sys.argv.(0) end Data_encoding.(obj1 (req "addr" P2p_types.addr_encoding)) (function Non_private_sandbox addr -> Some addr | _ -> None) (fun addr -> Non_private_sandbox addr) let (//) = Filename.concat let store_dir data_dir = data_dir // "store" let context_dir data_dir = data_dir // "context" let protocol_dir data_dir = data_dir // "protocol" let lock_file data_dir = data_dir // "lock" let init_logger ?verbosity (log_config : Node_config_file.log) = begin match verbosity with | Some level -> Lwt_log_core.add_rule "*" level | None -> Lwt_log_core.add_rule "*" log_config.default_level ; let rules = match Sys.getenv "TEZOS_LOG" with | rules -> Some rules | exception Not_found -> match Sys.getenv "LWT_LOG" with | rules -> Some rules | exception Not_found -> log_config.rules in Utils.iter_option ~f:Lwt_log_core.load_rules rules end ; Logging.init ~template:log_config.template log_config.output let init_node ?sandbox (config : Node_config_file.t) = let patch_context json ctxt = let module Proto = (val State.Registred_protocol.get_exn genesis.protocol) in Lwt_utils.protect begin fun () -> Proto.configure_sandbox ctxt json end >|= function | Error err -> warn "@[Error while configuring ecoproto for the sandboxed mode:@ %a@]" pp_print_error err ; ctxt | Ok ctxt -> ctxt in begin match sandbox with | None -> Lwt.return_none | Some sandbox_param -> match sandbox_param with | None -> Lwt.return (Some (patch_context None)) | Some file -> Data_encoding_ezjsonm.read_file file >>= function | Error err -> lwt_warn "Can't parse sandbox parameters: %s" file >>= fun () -> lwt_debug "%a" pp_print_error err >>= fun () -> Lwt.return (Some (patch_context None)) | Ok json -> Lwt.return (Some (patch_context (Some json))) end >>= fun patch_context -> (* TODO "WARN" when pow is below our expectation. *) begin match config.net.listen_addr with | None -> lwt_log_notice "Not listening to P2P calls." >>= fun () -> return (None, None) | Some addr -> Node_config_file.resolve_listening_addrs addr >>= function | [] -> failwith "Cannot resolve P2P listening address: %S" addr | (addr, port) :: _ -> return (Some addr, Some port) end >>=? fun (listening_addr, listening_port) -> begin match listening_addr, sandbox with | Some addr, Some _ when Ipaddr.V6.(compare addr unspecified) = 0 -> return None | Some addr, Some _ when not (Ipaddr.V6.is_private addr) -> fail (Non_private_sandbox addr) | None, Some _ -> return None | _ -> (Node_config_file.resolve_bootstrap_addrs config.net.bootstrap_peers) >>= fun trusted_points -> Node_identity_file.read (config.data_dir // Node_identity_file.default_name) >>=? fun identity -> lwt_log_notice "Peer's global id: %a" P2p.Peer_id.pp identity.peer_id >>= fun () -> let p2p_config : P2p.config = { listening_addr ; listening_port ; trusted_points ; peers_file = (config.data_dir // "peers.json") ; closed_network = config.net.closed ; identity ; proof_of_work_target = Crypto_box.make_target config.net.expected_pow ; } in return (Some (p2p_config, config.net.limits)) end >>=? fun p2p_config -> let node_config : Node.config = { genesis ; patch_context ; store_root = store_dir config.data_dir ; context_root = context_dir config.data_dir ; p2p = p2p_config ; test_network_max_tll = Some (48 * 3600) ; (* 2 days *) bootstrap_threshold = config.shell.bootstrap_threshold ; } in Node.create node_config config.shell.timeout let () = let old_hook = !Lwt.async_exception_hook in Lwt.async_exception_hook := function | Ssl.Read_error _ -> () | Ssl.Write_error _ -> () | exn -> old_hook exn let init_rpc (rpc_config: Node_config_file.rpc) node = match rpc_config.listen_addr with | None -> lwt_log_notice "Not listening to RPC calls." >>= fun () -> return None | Some addr -> Node_config_file.resolve_rpc_listening_addrs addr >>= function | [] -> failwith "Cannot resolve listening address: %S" addr | (addr, port) :: _ -> let host = Ipaddr.V6.to_string addr in let dir = Node_rpc.build_rpc_directory node in let mode = match rpc_config.tls with | None -> `TCP (`Port port) | Some { cert ; key } -> `TLS (`Crt_file_path cert, `Key_file_path key, `No_password, `Port port) in lwt_log_notice "Starting the RPC server listening on port %d%s." port (if rpc_config.tls = None then "" else " (TLS enabled)") >>= fun () -> RPC_server.launch ~host mode dir rpc_config.cors_origins rpc_config.cors_headers >>= fun server -> return (Some server) let init_signal () = let handler name id = try fatal_error "Received the %s signal, triggering shutdown." name ; Lwt_exit.exit id with _ -> () in ignore (Lwt_unix.on_signal Sys.sigint (handler "INT") : Lwt_unix.signal_handler_id) ; ignore (Lwt_unix.on_signal Sys.sigterm (handler "TERM") : Lwt_unix.signal_handler_id) let run ?verbosity ?sandbox (config : Node_config_file.t) = Node_data_version.ensure_data_dir config.data_dir >>=? fun () -> Lwt_utils.Lock_file.create ~unlink_on_exit:true (lock_file config.data_dir) >>=? fun () -> init_signal () ; init_logger ?verbosity config.log >>= fun () -> Updater.init (protocol_dir config.data_dir) ; lwt_log_notice "Starting the Tezos node..." >>= fun () -> init_node ?sandbox config >>=? fun node -> init_rpc config.rpc node >>=? fun rpc -> lwt_log_notice "The Tezos node is now running!" >>= fun () -> Lwt_exit.termination_thread >>= fun x -> lwt_log_notice "Shutting down the Tezos node..." >>= fun () -> Node.shutdown node >>= fun () -> lwt_log_notice "Shutting down the RPC server..." >>= fun () -> Lwt_utils.may ~f:RPC_server.shutdown rpc >>= fun () -> lwt_log_notice "BYE (%d)" x >>= fun () -> Logging.close () >>= fun () -> return () let process sandbox verbosity args = let verbosity = match verbosity with | [] -> None | [_] -> Some Logging.Info | _ -> Some Logging.Debug in let run = Node_shared_arg.read_and_patch_config_file ~ignore_bootstrap_peers:(match sandbox with | Some _ -> true | None -> false) args >>=? fun config -> begin match sandbox with | Some _ -> if config.data_dir = Node_config_file.default_data_dir then failwith "Cannot use default data directory while in sandbox mode" else return () | None -> return () end >>=? fun () -> Lwt_utils.Lock_file.is_locked (lock_file config.data_dir) >>=? function | false -> run ?sandbox ?verbosity config | true -> failwith "Data directory is locked by another process" in match Lwt_main.run run with | Ok () -> `Ok () | Error err -> `Error (false, Format.asprintf "%a" pp_print_error err) module Term = struct let verbosity = let open Cmdliner in let doc = "Increase log level. Using $(b,-v) is equivalent to \ using $(b,TEZOS_LOG='* -> info'), and $(b,-vv) is equivalent to using \ $(b,TEZOS_LOG='* -> debug')." in Arg.(value & flag_all & info ~docs:Node_shared_arg.Manpage.misc_section ~doc ["v"]) let sandbox = let open Cmdliner in let doc = "Run the daemon in sandbox mode. \ P2P to non-localhost addresses are disabled, and constants of \ the economic protocol can be altered with an optional JSON file. \ $(b,IMPORTANT): Using sandbox mode affects the node state and \ subsequent runs of Tezos node must also use sandbox mode. \ In order to run the node in normal mode afterwards, a full reset \ must be performed (by removing the node's data directory)." in Arg.(value & opt ~vopt:(Some None) (some (some string)) None & info ~docs:Node_shared_arg.Manpage.misc_section ~doc ~docv:"FILE.json" ["sandbox"]) let term = Cmdliner.Term.(ret (const process $ sandbox $ verbosity $ Node_shared_arg.Term.args)) end module Manpage = struct let command_description = "The $(b,run) command is meant to run the Tezos node. \ Most of its command line arguments corresponds to config file \ entries, and will have priority over the latter if used." let description = [ `S "DESCRIPTION" ; `P command_description ; ] let debug = let log_sections = String.concat " " (List.rev !Logging.sections) in [ `S "DEBUG" ; `P ("The environment variable $(b,TEZOS_LOG) is used to fine-tune \ what is going to be logged. The syntax is \ $(b,TEZOS_LOG='
 ->  [ ; ...]') \ where section is one of $(i," ^ log_sections ^ ") and level is one of $(i,fatal), $(i,error), $(i,warn), \ $(i,notice), $(i,info) or $(i,debug). \ A $(b,*) can be used as a wildcard \ in sections, i.e. $(b, client* -> debug). \ The rules are matched left to right, \ therefore the leftmost rule is highest priority ." ) ; ] let examples = [ `S "EXAMPLES" ; `I ("$(b,Run in sandbox mode listening to RPC commands \ at localhost port 8732)", "$(mname) run --sandbox --data-dir /custom/data/dir \ --rpc-addr localhost:8732" ) ; `I ("$(b,Run a node that accepts network connections)", "$(mname) run" ) ; ] let man = description @ Node_shared_arg.Manpage.args @ examples @ Node_shared_arg.Manpage.bugs let info = Cmdliner.Term.info ~doc:"Run the Tezos node" ~man "run" end let cmd = Term.term, Manpage.info