Shell: Improve the CLI of tezos-node
* Use subcommands: * `tezos_node run` * `tezos_node config` * `tezos_node identity` * Regroup all on-disk data in `--data-dir` * Split `Node_main` in multiple files. * Add DNS resolution for `--net-addr` and `--rpc-addr` * Hardcode `bootstrap.tezos.com` as bootstrap peer(s) * Add `--no-bootstrap-peers` * Rename `--expected-connections` -> `--connections`
This commit is contained in:
parent
dc7c692f87
commit
6d41b3d38c
173
README.md
173
README.md
@ -61,7 +61,7 @@ Running the node in a sandbox
|
|||||||
To run a single instance of a Tezos node in sandbox mode:
|
To run a single instance of a Tezos node in sandbox mode:
|
||||||
|
|
||||||
```
|
```
|
||||||
./tezos-node --sandbox --rpc-addr :::8732
|
./tezos-node run --sandbox --rpc-addr localhost:8732
|
||||||
```
|
```
|
||||||
|
|
||||||
This "sandboxed" node will not participate in the P2P network, but will accept
|
This "sandboxed" node will not participate in the P2P network, but will accept
|
||||||
@ -77,12 +77,12 @@ test network. Use the following command to run a node that will accept incoming
|
|||||||
connections:
|
connections:
|
||||||
|
|
||||||
```
|
```
|
||||||
./tezos-node --generate-identity --expected-pow 24.
|
./tezos-node identity generate 24.
|
||||||
```
|
```
|
||||||
|
|
||||||
This will first generate a new node identity and compute the associated stamp
|
This will first generate a new node identity and compute the
|
||||||
of proof-of-work. Then, the node will listen to connections coming in on
|
associated stamp of proof-of-work. Then, the node will listen to
|
||||||
`0.0.0.0:9732` (and`[::]:9732`). All used data is stored at
|
connections coming in on `[::]:9732`. All used data is stored at
|
||||||
`$HOME/.tezos-node/`. For example, the default configuration file is
|
`$HOME/.tezos-node/`. For example, the default configuration file is
|
||||||
at `$HOME/.tezos-node/config.json`.
|
at `$HOME/.tezos-node/config.json`.
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ command will generate it and replace the default values with the values from
|
|||||||
the command line:
|
the command line:
|
||||||
|
|
||||||
```
|
```
|
||||||
./tezos-node --base-dir "$dir" --net-addr 127.0.0.1:9733
|
./tezos-node run --data-dir "$dir" --net-addr localhost:9733
|
||||||
```
|
```
|
||||||
|
|
||||||
The Tezos server has a built-in mechanism to discover peers on the local
|
The Tezos server has a built-in mechanism to discover peers on the local
|
||||||
@ -108,8 +108,9 @@ initial peers, either by editing the option `net.bootstrap-peers` in the
|
|||||||
`config.json` file, or by specifying a command line parameter:
|
`config.json` file, or by specifying a command line parameter:
|
||||||
|
|
||||||
```
|
```
|
||||||
./tezos-node --base-dir "$dir" --net-addr 127.0.0.1:2023 \
|
./tezos-node run \
|
||||||
--peer 127.0.0.1:2021 --peer 127.0.0.1:2022
|
--data-dir "$dir" --net-addr localhost:2023 \
|
||||||
|
--peer localhost:2021 --peer localhost:2022
|
||||||
```
|
```
|
||||||
|
|
||||||
If `"$dir"/config.json` exists, the command line options override those
|
If `"$dir"/config.json` exists, the command line options override those
|
||||||
@ -119,10 +120,154 @@ to reset or to update the file according to the command line parameters
|
|||||||
with the following commands line:
|
with the following commands line:
|
||||||
|
|
||||||
```
|
```
|
||||||
./tezos-node --reset-config --base-dir "$dir" --net-addr 127.0.0.1:9733
|
./tezos-node config reset --data-dir "$dir" --net-addr localhost:9733
|
||||||
./tezos-node --update-config --base-dir "$dir" --net-addr 127.0.0.1:9734
|
./tezos-node config update --data-dir "$dir" --net-addr localhost:9734
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Configuration options
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Here is an example configuration file with all parameters
|
||||||
|
specified. Most of the time it uses default values, except for cases
|
||||||
|
where the default is not explanatory enough (i.e. "bootstrap-peers" is
|
||||||
|
an empty list by default). Comments are not allowed in JSON, so this
|
||||||
|
configuration file would not parse. They are just provided here to
|
||||||
|
help writing your own configuration file if needed.
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
|
||||||
|
/* Location of the data dir on disk. */
|
||||||
|
|
||||||
|
"data-dir": "/home/tezos/my_data_dir"
|
||||||
|
|
||||||
|
/* Configuration of net parameters */
|
||||||
|
|
||||||
|
"net": {
|
||||||
|
|
||||||
|
/* Floating point number between 0 and 256 that represents a
|
||||||
|
difficulty, 24 signifies for example that at least 24 leading
|
||||||
|
zeroes are expected in the hash. */
|
||||||
|
|
||||||
|
"expected-proof-of-work": 24.5,
|
||||||
|
|
||||||
|
/* List of hosts. Tezos can connect to both IPv6 and IPv4
|
||||||
|
hosts. If the port is not specified, default port 9732 will be
|
||||||
|
assumed. */
|
||||||
|
|
||||||
|
"bootstrap-peers": ["::1:10732", "::ffff:192.168.1.3:9733", "mynode.tezos.com"],
|
||||||
|
|
||||||
|
/* Specify if the network is closed or not. A closed network
|
||||||
|
allows only peers listed in "bootstrap-peers". */
|
||||||
|
|
||||||
|
"closed": false,
|
||||||
|
|
||||||
|
/* Network limits */
|
||||||
|
|
||||||
|
"limits": {
|
||||||
|
|
||||||
|
/* Delay granted to a peer to perform authentication, in
|
||||||
|
seconds. */
|
||||||
|
|
||||||
|
"authentication-timeout": 5,
|
||||||
|
|
||||||
|
/* Strict minimum number of connections (triggers an urgent
|
||||||
|
maintenance). */
|
||||||
|
|
||||||
|
"min-connections": 50,
|
||||||
|
|
||||||
|
/* Targeted number of connections to reach when bootstraping /
|
||||||
|
maintaining. */
|
||||||
|
|
||||||
|
"expected-connections": 100,
|
||||||
|
|
||||||
|
/* Maximum number of connections (exceeding peers are
|
||||||
|
disconnected). */
|
||||||
|
|
||||||
|
"max-connections": 200,
|
||||||
|
|
||||||
|
/* Number above which pending incoming connections are
|
||||||
|
immediately rejected. */
|
||||||
|
|
||||||
|
"backlog": 20,
|
||||||
|
|
||||||
|
/* Maximum allowed number of incoming connections that are
|
||||||
|
pending authentication. */
|
||||||
|
|
||||||
|
"max-incoming-connections": 20,
|
||||||
|
|
||||||
|
/* Max download and upload speeds in KiB/s. */
|
||||||
|
|
||||||
|
"max-download-speed": 1024,
|
||||||
|
"max-upload-speed": 1024,
|
||||||
|
|
||||||
|
/* Size of the buffer passed to read(2). */
|
||||||
|
|
||||||
|
"read-buffer-size": 16384,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Configuration of rpc parameters */
|
||||||
|
|
||||||
|
"rpc": {
|
||||||
|
|
||||||
|
/* Host to listen to. If the port is not specified, the default
|
||||||
|
port 8732 will be assumed. */
|
||||||
|
|
||||||
|
"listen-addr": "localhost:8733",
|
||||||
|
|
||||||
|
/* Cross Origin Resource Sharing parameters, see
|
||||||
|
https://en.wikipedia.org/wiki/Cross-origin_resource_sharing. */
|
||||||
|
|
||||||
|
"cors-origin": [],
|
||||||
|
"cors-headers": [],
|
||||||
|
|
||||||
|
/* Certificate and key files (necessary when TLS is used). */
|
||||||
|
|
||||||
|
"crt": "tezos-node.crt",
|
||||||
|
"key": "tezos-node.key"
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Configuration of log parameters */
|
||||||
|
|
||||||
|
"log": {
|
||||||
|
|
||||||
|
/* Output for the logging function. Either "stdout", "stderr" or
|
||||||
|
the name of a log file . */
|
||||||
|
|
||||||
|
"output": "tezos-node.log",
|
||||||
|
|
||||||
|
/* Verbosity level: one of 'fatal', 'error', 'warn', 'notice',
|
||||||
|
'info', 'debug'. */
|
||||||
|
|
||||||
|
"level": "info",
|
||||||
|
|
||||||
|
/* Fine-grained logging instructions. Same format as described in
|
||||||
|
`tezos-node run --help`, DEBUG section. In the example below,
|
||||||
|
sections "net" and all sections starting by "client" will have
|
||||||
|
their messages logged up to the debug level, whereas the rest of
|
||||||
|
log sections will be logged up to the notice level. */
|
||||||
|
|
||||||
|
"rules": "client* -> debug, net -> debug, * -> notice",
|
||||||
|
|
||||||
|
/* Format for the log file, see
|
||||||
|
http://ocsigen.org/lwt/dev/api/Lwt_log_core#2_Logtemplates. */
|
||||||
|
|
||||||
|
"template": "$(date) - $(section): $(message)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Debugging
|
||||||
|
---------
|
||||||
|
|
||||||
|
It is possible to set independant log levels for different logging
|
||||||
|
sections in Tezos, as well as specifying an output file for
|
||||||
|
logging. See the description of log parameters above as well as
|
||||||
|
documentation under the DEBUG section diplayed by `tezos-node run
|
||||||
|
--help'.
|
||||||
|
|
||||||
|
|
||||||
JSON/RPC interface
|
JSON/RPC interface
|
||||||
------------------
|
------------------
|
||||||
@ -134,7 +279,7 @@ Typically, if you are not trying to run a local network and just want to
|
|||||||
explore the RPC, you would run:
|
explore the RPC, you would run:
|
||||||
|
|
||||||
```
|
```
|
||||||
./tezos-node --sandbox --rpc-addr :::8732
|
./tezos-node run --sandbox --rpc-addr localhost
|
||||||
```
|
```
|
||||||
|
|
||||||
The RPC interface is self-documented and the `tezos-client` executable is able
|
The RPC interface is self-documented and the `tezos-client` executable is able
|
||||||
@ -163,9 +308,9 @@ Note: you can get the same information, but as a raw JSON object, with a simple
|
|||||||
HTTP request:
|
HTTP request:
|
||||||
|
|
||||||
```
|
```
|
||||||
wget --post-data '{ "recursive": true }' -O - http://127.0.0.1:8732/describe
|
wget --post-data '{ "recursive": true }' -O - http://localhost:8732/describe
|
||||||
wget --post-data '{ "recursive": true }' -O - http://127.0.0.1:8732/describe/blocks/genesis
|
wget --post-data '{ "recursive": true }' -O - http://localhost:8732/describe/blocks/genesis
|
||||||
wget -O - http://127.0.0.1:8732/describe/blocks/genesis/hash
|
wget -O - http://localhost:8732/describe/blocks/genesis/hash
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@ S node/shell
|
|||||||
B node/shell
|
B node/shell
|
||||||
S node/db
|
S node/db
|
||||||
B node/db
|
B node/db
|
||||||
|
S node/main
|
||||||
|
B node/main
|
||||||
S minutils
|
S minutils
|
||||||
B minutils
|
B minutils
|
||||||
S utils
|
S utils
|
||||||
|
18
src/Makefile
18
src/Makefile
@ -187,6 +187,7 @@ UTILS_PACKAGES := \
|
|||||||
base64 \
|
base64 \
|
||||||
calendar \
|
calendar \
|
||||||
ezjsonm \
|
ezjsonm \
|
||||||
|
ipaddr.unix \
|
||||||
mtime.os \
|
mtime.os \
|
||||||
sodium \
|
sodium \
|
||||||
zarith \
|
zarith \
|
||||||
@ -330,7 +331,21 @@ NODE_LIB_IMPLS := \
|
|||||||
node/shell/node.ml \
|
node/shell/node.ml \
|
||||||
node/shell/node_rpc.ml \
|
node/shell/node_rpc.ml \
|
||||||
|
|
||||||
|
NODE_INTFS := \
|
||||||
|
node/main/node_identity_file.mli \
|
||||||
|
node/main/node_config_file.mli \
|
||||||
|
node/main/node_shared_arg.mli \
|
||||||
|
node/main/node_run_command.mli \
|
||||||
|
node/main/node_config_command.mli \
|
||||||
|
node/main/node_identity_command.mli \
|
||||||
|
|
||||||
NODE_IMPLS := \
|
NODE_IMPLS := \
|
||||||
|
node/main/node_identity_file.ml \
|
||||||
|
node/main/node_config_file.ml \
|
||||||
|
node/main/node_shared_arg.ml \
|
||||||
|
node/main/node_run_command.ml \
|
||||||
|
node/main/node_config_command.ml \
|
||||||
|
node/main/node_identity_command.ml \
|
||||||
node_main.ml \
|
node_main.ml \
|
||||||
|
|
||||||
NODE_PACKAGES := \
|
NODE_PACKAGES := \
|
||||||
@ -351,11 +366,12 @@ EMBEDDED_NODE_PROTOCOLS := \
|
|||||||
|
|
||||||
NODE_OBJS := \
|
NODE_OBJS := \
|
||||||
${NODE_IMPLS:.ml=.cmx} ${NODE_IMPLS:.ml=.ml.deps} \
|
${NODE_IMPLS:.ml=.cmx} ${NODE_IMPLS:.ml=.ml.deps} \
|
||||||
|
${NODE_INTFS:.mli=.cmi} ${NODE_INTFS:.mli=.mli.deps} \
|
||||||
${NODE_LIB_IMPLS:.ml=.cmx} ${NODE_LIB_IMPLS:.ml=.ml.deps} \
|
${NODE_LIB_IMPLS:.ml=.cmx} ${NODE_LIB_IMPLS:.ml=.ml.deps} \
|
||||||
${NODE_LIB_INTFS:.mli=.cmi} ${NODE_LIB_INTFS:.mli=.mli.deps} \
|
${NODE_LIB_INTFS:.mli=.cmi} ${NODE_LIB_INTFS:.mli=.mli.deps} \
|
||||||
${TZNODE}
|
${TZNODE}
|
||||||
${NODE_OBJS}: PACKAGES=${NODE_PACKAGES}
|
${NODE_OBJS}: PACKAGES=${NODE_PACKAGES}
|
||||||
${NODE_OBJS}: SOURCE_DIRECTORIES=minutils utils compiler node/db node/net node/updater node/shell
|
${NODE_OBJS}: SOURCE_DIRECTORIES=minutils utils compiler node/db node/net node/updater node/shell node/main
|
||||||
${NODE_OBJS}: TARGET="(node.cmxa)"
|
${NODE_OBJS}: TARGET="(node.cmxa)"
|
||||||
${NODE_OBJS}: OPENED_MODULES=Error_monad Hash Utils
|
${NODE_OBJS}: OPENED_MODULES=Error_monad Hash Utils
|
||||||
|
|
||||||
|
@ -86,6 +86,10 @@ let list_rev_sub l n =
|
|||||||
|
|
||||||
let list_sub l n = list_rev_sub l n |> List.rev
|
let list_sub l n = list_rev_sub l n |> List.rev
|
||||||
|
|
||||||
|
let list_hd_opt = function
|
||||||
|
| [] -> None
|
||||||
|
| h :: _ -> Some h
|
||||||
|
|
||||||
let display_paragraph ppf description =
|
let display_paragraph ppf description =
|
||||||
Format.fprintf ppf "@[%a@]"
|
Format.fprintf ppf "@[%a@]"
|
||||||
(Format.pp_print_list ~pp_sep:Format.pp_print_newline
|
(Format.pp_print_list ~pp_sep:Format.pp_print_newline
|
||||||
@ -200,3 +204,50 @@ let select n l =
|
|||||||
| x :: xs -> loop (pred n) (x :: acc) xs
|
| x :: xs -> loop (pred n) (x :: acc) xs
|
||||||
in
|
in
|
||||||
loop n [] l
|
loop n [] l
|
||||||
|
|
||||||
|
|
||||||
|
let mem_char s c =
|
||||||
|
match String.index s c with
|
||||||
|
| exception Not_found -> false
|
||||||
|
| _ -> true
|
||||||
|
|
||||||
|
let check_port port =
|
||||||
|
if mem_char port '[' || mem_char port ']' || mem_char port ':' then
|
||||||
|
invalid_arg "Utils.parse_addr_port (invalid character in port)"
|
||||||
|
|
||||||
|
let parse_addr_port s =
|
||||||
|
let len = String.length s in
|
||||||
|
if len = 0 then
|
||||||
|
("", "")
|
||||||
|
else if s.[0] = '[' then begin (* inline IPv6 *)
|
||||||
|
match String.rindex s ']' with
|
||||||
|
| exception Not_found ->
|
||||||
|
invalid_arg "Utils.parse_addr_port (missing ']')"
|
||||||
|
| pos ->
|
||||||
|
let addr = String.sub s 1 (pos - 1) in
|
||||||
|
let port =
|
||||||
|
if pos = len - 1 then
|
||||||
|
""
|
||||||
|
else if s.[pos+1] <> ':' then
|
||||||
|
invalid_arg "Utils.parse_addr_port (unexpected char after ']')"
|
||||||
|
else
|
||||||
|
String.sub s (pos + 2) (len - pos - 2) in
|
||||||
|
check_port port ;
|
||||||
|
addr, port
|
||||||
|
end else begin
|
||||||
|
match String.rindex s ']' with
|
||||||
|
| _pos ->
|
||||||
|
invalid_arg "Utils.parse_addr_port (unexpected char ']')"
|
||||||
|
| exception Not_found ->
|
||||||
|
match String.index s ':' with
|
||||||
|
| exception _ -> s, ""
|
||||||
|
| pos ->
|
||||||
|
match String.index_from s (pos+1) ':' with
|
||||||
|
| exception _ ->
|
||||||
|
let addr = String.sub s 0 pos in
|
||||||
|
let port = String.sub s (pos + 1) (len - pos - 1) in
|
||||||
|
check_port port ;
|
||||||
|
addr, port
|
||||||
|
| _pos ->
|
||||||
|
invalid_arg "split_url_port: IPv6 addresses must be bracketed"
|
||||||
|
end
|
||||||
|
@ -39,6 +39,7 @@ val filter_map: ('a -> 'b option) -> 'a list -> 'b list
|
|||||||
val list_rev_sub : 'a list -> int -> 'a list
|
val list_rev_sub : 'a list -> int -> 'a list
|
||||||
(** [list_sub l n] is l capped to max n elements *)
|
(** [list_sub l n] is l capped to max n elements *)
|
||||||
val list_sub: 'a list -> int -> 'a list
|
val list_sub: 'a list -> int -> 'a list
|
||||||
|
val list_hd_opt: 'a list -> 'a option
|
||||||
|
|
||||||
val finalize: (unit -> 'a) -> (unit -> unit) -> 'a
|
val finalize: (unit -> 'a) -> (unit -> unit) -> 'a
|
||||||
|
|
||||||
@ -66,3 +67,7 @@ module Bounded(E: Set.OrderedType) : sig
|
|||||||
end
|
end
|
||||||
|
|
||||||
val select: int -> 'a list -> 'a * 'a list
|
val select: int -> 'a list -> 'a * 'a list
|
||||||
|
|
||||||
|
(** [split_url_port uri] is (node, service) where [node] is the DNS or
|
||||||
|
IP and service is the optional port number or service name. *)
|
||||||
|
val parse_addr_port: string -> string * string
|
||||||
|
140
src/node/main/node_config_command.ml
Normal file
140
src/node/main/node_config_command.ml
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
(**************************************************************************)
|
||||||
|
(* *)
|
||||||
|
(* Copyright (c) 2014 - 2016. *)
|
||||||
|
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
|
||||||
|
(* *)
|
||||||
|
(* All rights reserved. No warranty, explicit or implicit, provided. *)
|
||||||
|
(* *)
|
||||||
|
(**************************************************************************)
|
||||||
|
|
||||||
|
(** Commands *)
|
||||||
|
|
||||||
|
let show (args : Node_shared_arg.t) =
|
||||||
|
if not @@ Sys.file_exists args.config_file then
|
||||||
|
Format.eprintf
|
||||||
|
"\n\
|
||||||
|
Warning: no config file at %s,\n\
|
||||||
|
\ displaying the default configuration.\n@."
|
||||||
|
args.config_file ;
|
||||||
|
Node_shared_arg.read_and_patch_config_file args >>=? fun cfg ->
|
||||||
|
Node_config_file.check cfg >>= fun () ->
|
||||||
|
print_endline @@ Node_config_file.to_string cfg ;
|
||||||
|
return ()
|
||||||
|
|
||||||
|
let reset (args : Node_shared_arg.t) =
|
||||||
|
if Sys.file_exists args.config_file then
|
||||||
|
Format.eprintf
|
||||||
|
"Ignoring previous configuration file: %s.@."
|
||||||
|
args.config_file ;
|
||||||
|
Node_shared_arg.read_and_patch_config_file args >>=? fun cfg ->
|
||||||
|
Node_config_file.check cfg >>= fun () ->
|
||||||
|
Node_config_file.write args.config_file cfg
|
||||||
|
|
||||||
|
let init (args : Node_shared_arg.t) =
|
||||||
|
if Sys.file_exists args.config_file then
|
||||||
|
failwith
|
||||||
|
"Pre-existant config file at %s, use `reset`."
|
||||||
|
args.config_file
|
||||||
|
else
|
||||||
|
Node_shared_arg.read_and_patch_config_file args >>=? fun cfg ->
|
||||||
|
Node_config_file.check cfg >>= fun () ->
|
||||||
|
Node_config_file.write args.config_file cfg
|
||||||
|
|
||||||
|
let update (args : Node_shared_arg.t) =
|
||||||
|
if not (Sys.file_exists args.config_file) then
|
||||||
|
failwith
|
||||||
|
"Missing configuration file at %s. \
|
||||||
|
Use `%s config init [options]` to generate a new file"
|
||||||
|
args.config_file Sys.argv.(0)
|
||||||
|
else
|
||||||
|
Node_shared_arg.read_and_patch_config_file args >>=? fun cfg ->
|
||||||
|
Node_config_file.check cfg >>= fun () ->
|
||||||
|
Node_config_file.write args.config_file cfg
|
||||||
|
|
||||||
|
(** Main *)
|
||||||
|
|
||||||
|
module Term = struct
|
||||||
|
|
||||||
|
type subcommand = Show | Reset | Init | Update
|
||||||
|
|
||||||
|
let process subcommand args =
|
||||||
|
let res =
|
||||||
|
match subcommand with
|
||||||
|
| Show -> show args
|
||||||
|
| Reset -> reset args
|
||||||
|
| Init -> init args
|
||||||
|
| Update -> update args in
|
||||||
|
match Lwt_main.run res with
|
||||||
|
| Ok () -> `Ok ()
|
||||||
|
| Error err -> `Error (false, Format.asprintf "%a" pp_print_error err)
|
||||||
|
|
||||||
|
let subcommand_arg =
|
||||||
|
let parser = function
|
||||||
|
| "show" -> `Ok Show
|
||||||
|
| "reset" -> `Ok Reset
|
||||||
|
| "init" -> `Ok Init
|
||||||
|
| "update" -> `Ok Update
|
||||||
|
| s -> `Error ("invalid argument: " ^ s)
|
||||||
|
and printer ppf = function
|
||||||
|
| Show -> Format.fprintf ppf "show"
|
||||||
|
| Reset -> Format.fprintf ppf "reset"
|
||||||
|
| Init -> Format.fprintf ppf "init"
|
||||||
|
| Update -> Format.fprintf ppf "update" in
|
||||||
|
let open Cmdliner.Arg in
|
||||||
|
let doc =
|
||||||
|
"Operation to perform. \
|
||||||
|
Possible values: $(b,show), $(b,reset), $(b,init), $(b,save)." in
|
||||||
|
value & pos 0 (parser, printer) Show & info [] ~docv:"OPERATION" ~doc
|
||||||
|
|
||||||
|
let term =
|
||||||
|
let open Cmdliner.Term in
|
||||||
|
ret (const process $ subcommand_arg $ Node_shared_arg.Term.args)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
module Manpage = struct
|
||||||
|
|
||||||
|
let command_description =
|
||||||
|
"The $(b,config) command is meant to inspect and amend the \
|
||||||
|
configuration of the Tezos node. \
|
||||||
|
This command is complementary to manually editing the tezos \
|
||||||
|
node configuration file. Its arguments are a subset of the $(i,run) \
|
||||||
|
command ones."
|
||||||
|
|
||||||
|
let description = [
|
||||||
|
`S "DESCRIPTION" ;
|
||||||
|
`P (command_description ^ " Several operations are possible: ");
|
||||||
|
`P "$(b,show) reads, parses and displays Tezos current config file. \
|
||||||
|
Use this command to see exactly what config file will be used by \
|
||||||
|
Tezos. If additional command-line arguments are provided, \
|
||||||
|
the displayed configuration will be amended accordingly. \
|
||||||
|
This is the default operation." ;
|
||||||
|
`P "$(b,reset) will overwrite the current configuration file with a \
|
||||||
|
factory default one. \
|
||||||
|
If additional command-line arguments are provided, \
|
||||||
|
they will amend the generated file. \
|
||||||
|
It assumes that a configuration file already exists, \
|
||||||
|
and will abort otherwise." ;
|
||||||
|
`P "$(b,init) is like reset but assumes that \
|
||||||
|
no configuration file is present, \
|
||||||
|
and will abort other otherwise." ;
|
||||||
|
`P "$(b,save) is the main option to edit the configuration file of Tezos. \
|
||||||
|
It will parse command line arguments and add or replace corresponding \
|
||||||
|
entries in the Tezos configuration file."
|
||||||
|
]
|
||||||
|
|
||||||
|
let man =
|
||||||
|
description @
|
||||||
|
Node_shared_arg.Manpage.args @
|
||||||
|
Node_shared_arg.Manpage.bugs
|
||||||
|
|
||||||
|
let info =
|
||||||
|
Cmdliner.Term.info
|
||||||
|
~doc:"Manage node configuration"
|
||||||
|
~man
|
||||||
|
"config"
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
let cmd =
|
||||||
|
Term.term, Manpage.info
|
14
src/node/main/node_config_command.mli
Normal file
14
src/node/main/node_config_command.mli
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
(**************************************************************************)
|
||||||
|
(* *)
|
||||||
|
(* Copyright (c) 2014 - 2016. *)
|
||||||
|
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
|
||||||
|
(* *)
|
||||||
|
(* All rights reserved. No warranty, explicit or implicit, provided. *)
|
||||||
|
(* *)
|
||||||
|
(**************************************************************************)
|
||||||
|
|
||||||
|
val cmd : unit Cmdliner.Term.t * Cmdliner.Term.info
|
||||||
|
|
||||||
|
module Manpage : sig
|
||||||
|
val command_description: string
|
||||||
|
end
|
398
src/node/main/node_config_file.ml
Normal file
398
src/node/main/node_config_file.ml
Normal file
@ -0,0 +1,398 @@
|
|||||||
|
(**************************************************************************)
|
||||||
|
(* *)
|
||||||
|
(* Copyright (c) 2014 - 2016. *)
|
||||||
|
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
|
||||||
|
(* *)
|
||||||
|
(* All rights reserved. No warranty, explicit or implicit, provided. *)
|
||||||
|
(* *)
|
||||||
|
(**************************************************************************)
|
||||||
|
|
||||||
|
open P2p_types
|
||||||
|
|
||||||
|
let (//) = Filename.concat
|
||||||
|
|
||||||
|
let home =
|
||||||
|
try Sys.getenv "HOME"
|
||||||
|
with Not_found -> "/root"
|
||||||
|
|
||||||
|
let default_data_dir = home // ".tezos-node"
|
||||||
|
let default_net_port = 9732
|
||||||
|
let default_rpc_port = 8732
|
||||||
|
|
||||||
|
type t = {
|
||||||
|
data_dir : string ;
|
||||||
|
net : net ;
|
||||||
|
rpc : rpc ;
|
||||||
|
log : log ;
|
||||||
|
}
|
||||||
|
|
||||||
|
and net = {
|
||||||
|
expected_pow : float ;
|
||||||
|
bootstrap_peers : string list ;
|
||||||
|
listen_addr : string option ;
|
||||||
|
closed : bool ;
|
||||||
|
limits : P2p.limits ;
|
||||||
|
}
|
||||||
|
|
||||||
|
and rpc = {
|
||||||
|
listen_addr : string option ;
|
||||||
|
cors_origins : string list ;
|
||||||
|
cors_headers : string list ;
|
||||||
|
tls : tls option ;
|
||||||
|
}
|
||||||
|
|
||||||
|
and tls = {
|
||||||
|
cert : string ;
|
||||||
|
key : string ;
|
||||||
|
}
|
||||||
|
|
||||||
|
and log = {
|
||||||
|
output : Logging.Output.t ;
|
||||||
|
default_level : Logging.level ;
|
||||||
|
rules : string option ;
|
||||||
|
template : Logging.template ;
|
||||||
|
}
|
||||||
|
|
||||||
|
let default_net_limits : P2p.limits = {
|
||||||
|
authentification_timeout = 5. ;
|
||||||
|
min_connections = 50 ;
|
||||||
|
expected_connections = 100 ;
|
||||||
|
max_connections = 200 ;
|
||||||
|
backlog = 20 ;
|
||||||
|
max_incoming_connections = 20 ;
|
||||||
|
max_download_speed = None ;
|
||||||
|
max_upload_speed = None ;
|
||||||
|
read_buffer_size = 1 lsl 14 ;
|
||||||
|
read_queue_size = None ;
|
||||||
|
write_queue_size = None ;
|
||||||
|
incoming_app_message_queue_size = None ;
|
||||||
|
incoming_message_queue_size = None ;
|
||||||
|
outgoing_message_queue_size = None ;
|
||||||
|
}
|
||||||
|
|
||||||
|
let default_net = {
|
||||||
|
expected_pow = 24. ;
|
||||||
|
bootstrap_peers = ["bootstrap.tezos.com"] ;
|
||||||
|
listen_addr = Some ("[::]:" ^ string_of_int default_net_port) ;
|
||||||
|
closed = false ;
|
||||||
|
limits = default_net_limits ;
|
||||||
|
}
|
||||||
|
|
||||||
|
let default_rpc = {
|
||||||
|
listen_addr = None ;
|
||||||
|
cors_origins = [] ;
|
||||||
|
cors_headers = [] ;
|
||||||
|
tls = None ;
|
||||||
|
}
|
||||||
|
|
||||||
|
let default_log = {
|
||||||
|
output = Stderr ;
|
||||||
|
default_level = Notice ;
|
||||||
|
rules = None ;
|
||||||
|
template = Logging.default_template ;
|
||||||
|
}
|
||||||
|
|
||||||
|
let default_config = {
|
||||||
|
data_dir = default_data_dir ;
|
||||||
|
net = default_net ;
|
||||||
|
rpc = default_rpc ;
|
||||||
|
log = default_log ;
|
||||||
|
}
|
||||||
|
|
||||||
|
let limit : P2p.limits Data_encoding.t =
|
||||||
|
let open Data_encoding in
|
||||||
|
conv
|
||||||
|
(fun { P2p.authentification_timeout ;
|
||||||
|
min_connections ; expected_connections ; max_connections ;
|
||||||
|
backlog ; max_incoming_connections ;
|
||||||
|
max_download_speed ; max_upload_speed ;
|
||||||
|
read_buffer_size ; read_queue_size ; write_queue_size ;
|
||||||
|
incoming_app_message_queue_size ;
|
||||||
|
incoming_message_queue_size ; outgoing_message_queue_size } ->
|
||||||
|
( ( authentification_timeout, min_connections, expected_connections,
|
||||||
|
max_connections, backlog, max_incoming_connections,
|
||||||
|
max_download_speed, max_upload_speed) ,
|
||||||
|
( read_buffer_size, read_queue_size, write_queue_size,
|
||||||
|
incoming_app_message_queue_size,
|
||||||
|
incoming_message_queue_size, outgoing_message_queue_size )))
|
||||||
|
(fun ( ( authentification_timeout, min_connections, expected_connections,
|
||||||
|
max_connections, backlog, max_incoming_connections,
|
||||||
|
max_download_speed, max_upload_speed) ,
|
||||||
|
( read_buffer_size, read_queue_size, write_queue_size,
|
||||||
|
incoming_app_message_queue_size,
|
||||||
|
incoming_message_queue_size, outgoing_message_queue_size ) ) ->
|
||||||
|
{ authentification_timeout ; min_connections ; expected_connections ;
|
||||||
|
max_connections ; backlog ; max_incoming_connections ;
|
||||||
|
max_download_speed ; max_upload_speed ;
|
||||||
|
read_buffer_size ; read_queue_size ; write_queue_size ;
|
||||||
|
incoming_app_message_queue_size ;
|
||||||
|
incoming_message_queue_size ; outgoing_message_queue_size })
|
||||||
|
(merge_objs
|
||||||
|
(obj8
|
||||||
|
(dft "authentification-timeout"
|
||||||
|
float default_net_limits.authentification_timeout)
|
||||||
|
(dft "min-connections" int31
|
||||||
|
default_net_limits.min_connections)
|
||||||
|
(dft "expected-connections" int31
|
||||||
|
default_net_limits.expected_connections)
|
||||||
|
(dft "max-connections" int31
|
||||||
|
default_net_limits.max_connections)
|
||||||
|
(dft "backlog" int31
|
||||||
|
default_net_limits.backlog)
|
||||||
|
(dft "max-incoming-connections" int31
|
||||||
|
default_net_limits.max_incoming_connections)
|
||||||
|
(opt "max-download-speed" int31)
|
||||||
|
(opt "max-upload-speed" int31))
|
||||||
|
(obj6
|
||||||
|
(dft "read-buffer-size" int31
|
||||||
|
default_net_limits.read_buffer_size)
|
||||||
|
(opt "read-queue-size" int31)
|
||||||
|
(opt "write-queue-size" int31)
|
||||||
|
(opt "incoming-app-message-queue-size" int31)
|
||||||
|
(opt "incoming-message-queue-size" int31)
|
||||||
|
(opt "outgoing-message-queue-size" int31)))
|
||||||
|
|
||||||
|
let net =
|
||||||
|
let open Data_encoding in
|
||||||
|
conv
|
||||||
|
(fun { expected_pow ; bootstrap_peers ;
|
||||||
|
listen_addr ; closed ; limits } ->
|
||||||
|
( expected_pow, bootstrap_peers,
|
||||||
|
listen_addr, closed, limits ))
|
||||||
|
(fun ( expected_pow, bootstrap_peers,
|
||||||
|
listen_addr, closed, limits ) ->
|
||||||
|
{ expected_pow ; bootstrap_peers ;
|
||||||
|
listen_addr ; closed ; limits })
|
||||||
|
(obj5
|
||||||
|
(dft "expected-proof-or-work" float default_net.expected_pow)
|
||||||
|
(dft "bootstrap-peers"
|
||||||
|
(list string) default_net.bootstrap_peers)
|
||||||
|
(opt "listen-addr" string)
|
||||||
|
(dft "closed" bool false)
|
||||||
|
(dft "limits" limit default_net_limits))
|
||||||
|
|
||||||
|
let rpc : rpc Data_encoding.t =
|
||||||
|
let open Data_encoding in
|
||||||
|
conv
|
||||||
|
(fun { cors_origins ; cors_headers ; listen_addr ; tls } ->
|
||||||
|
let cert, key =
|
||||||
|
match tls with
|
||||||
|
| None -> None, None
|
||||||
|
| Some { cert ; key } -> Some cert, Some key in
|
||||||
|
(listen_addr, cors_origins, cors_headers, cert, key ))
|
||||||
|
(fun (listen_addr, cors_origins, cors_headers, cert, key ) ->
|
||||||
|
let tls =
|
||||||
|
match cert, key with
|
||||||
|
| None, _ | _, None -> None
|
||||||
|
| Some cert, Some key -> Some { cert ; key } in
|
||||||
|
{ listen_addr ; cors_origins ; cors_headers ; tls })
|
||||||
|
(obj5
|
||||||
|
(opt "listen-addr" string)
|
||||||
|
(dft "cors-origin" (list string) default_rpc.cors_origins)
|
||||||
|
(dft "cors-headers" (list string) default_rpc.cors_headers)
|
||||||
|
(opt "crt" string)
|
||||||
|
(opt "key" string))
|
||||||
|
|
||||||
|
let log =
|
||||||
|
let open Data_encoding in
|
||||||
|
conv
|
||||||
|
(fun {output ; default_level ; rules ; template } ->
|
||||||
|
(output, default_level, rules, template))
|
||||||
|
(fun (output, default_level, rules, template) ->
|
||||||
|
{ output ; default_level ; rules ; template })
|
||||||
|
(obj4
|
||||||
|
(dft "output" Logging.Output.encoding default_log.output)
|
||||||
|
(dft "level" Logging.level_encoding default_log.default_level)
|
||||||
|
(opt "rules" string)
|
||||||
|
(dft "template" string default_log.template))
|
||||||
|
|
||||||
|
let encoding =
|
||||||
|
let open Data_encoding in
|
||||||
|
conv
|
||||||
|
(fun { data_dir ; rpc ; net ; log } -> (data_dir, rpc, net, log))
|
||||||
|
(fun (data_dir, rpc, net, log) -> { data_dir ; rpc ; net ; log })
|
||||||
|
(obj4
|
||||||
|
(dft "data-dir" string default_data_dir)
|
||||||
|
(dft "rpc" rpc default_rpc)
|
||||||
|
(req "net" net)
|
||||||
|
(dft "log" log default_log))
|
||||||
|
|
||||||
|
let read fp =
|
||||||
|
if Sys.file_exists fp then begin
|
||||||
|
Data_encoding_ezjsonm.read_file fp >>=? fun json ->
|
||||||
|
try return (Data_encoding.Json.destruct encoding json)
|
||||||
|
with exn -> fail (Exn exn)
|
||||||
|
end else
|
||||||
|
return default_config
|
||||||
|
|
||||||
|
let write fp cfg =
|
||||||
|
Lwt_utils.create_dir ~perm:0o700 (Filename.dirname fp) >>= fun () ->
|
||||||
|
Data_encoding_ezjsonm.write_file fp
|
||||||
|
(Data_encoding.Json.construct encoding cfg)
|
||||||
|
|
||||||
|
let to_string cfg =
|
||||||
|
Data_encoding_ezjsonm.to_string
|
||||||
|
(Data_encoding.Json.construct encoding cfg)
|
||||||
|
|
||||||
|
let update
|
||||||
|
?data_dir
|
||||||
|
?min_connections
|
||||||
|
?expected_connections
|
||||||
|
?max_connections
|
||||||
|
?max_download_speed
|
||||||
|
?max_upload_speed
|
||||||
|
?expected_pow
|
||||||
|
?bootstrap_peers
|
||||||
|
?listen_addr
|
||||||
|
?rpc_listen_addr
|
||||||
|
?(closed = false)
|
||||||
|
?(cors_origins = [])
|
||||||
|
?(cors_headers = [])
|
||||||
|
?rpc_tls
|
||||||
|
?log_output
|
||||||
|
cfg =
|
||||||
|
let unopt_list ~default = function
|
||||||
|
| [] -> default
|
||||||
|
| l -> l in
|
||||||
|
let limits : P2p.limits = {
|
||||||
|
cfg.net.limits with
|
||||||
|
min_connections =
|
||||||
|
Utils.unopt
|
||||||
|
~default:cfg.net.limits.min_connections
|
||||||
|
min_connections ;
|
||||||
|
expected_connections =
|
||||||
|
Utils.unopt
|
||||||
|
~default:cfg.net.limits.expected_connections
|
||||||
|
expected_connections ;
|
||||||
|
max_connections =
|
||||||
|
Utils.unopt
|
||||||
|
~default:cfg.net.limits.max_connections
|
||||||
|
max_connections ;
|
||||||
|
max_download_speed =
|
||||||
|
Utils.first_some
|
||||||
|
max_download_speed cfg.net.limits.max_download_speed ;
|
||||||
|
max_upload_speed =
|
||||||
|
Utils.first_some
|
||||||
|
max_upload_speed cfg.net.limits.max_upload_speed ;
|
||||||
|
} in
|
||||||
|
let net : net = {
|
||||||
|
expected_pow =
|
||||||
|
Utils.unopt ~default:cfg.net.expected_pow expected_pow ;
|
||||||
|
bootstrap_peers =
|
||||||
|
Utils.unopt ~default:cfg.net.bootstrap_peers bootstrap_peers ;
|
||||||
|
listen_addr =
|
||||||
|
Utils.first_some listen_addr cfg.net.listen_addr ;
|
||||||
|
closed = cfg.net.closed || closed ;
|
||||||
|
limits ;
|
||||||
|
}
|
||||||
|
and rpc : rpc = {
|
||||||
|
listen_addr =
|
||||||
|
Utils.first_some rpc_listen_addr cfg.rpc.listen_addr ;
|
||||||
|
cors_origins =
|
||||||
|
unopt_list ~default:cfg.rpc.cors_origins cors_origins ;
|
||||||
|
cors_headers =
|
||||||
|
unopt_list ~default:cfg.rpc.cors_headers cors_headers ;
|
||||||
|
tls =
|
||||||
|
Utils.first_some rpc_tls cfg.rpc.tls ;
|
||||||
|
}
|
||||||
|
and log : log = {
|
||||||
|
cfg.log with
|
||||||
|
output = Utils.unopt ~default:cfg.log.output log_output ;
|
||||||
|
}
|
||||||
|
in
|
||||||
|
{ data_dir = Utils.unopt ~default:cfg.data_dir data_dir ;
|
||||||
|
net ; rpc ; log }
|
||||||
|
|
||||||
|
let resolve_addr ?default_port ?(passive = false) peer =
|
||||||
|
let addr, port = Utils.parse_addr_port peer in
|
||||||
|
let node = if addr = "" || addr = "_" then "::" else addr
|
||||||
|
and service =
|
||||||
|
match port, default_port with
|
||||||
|
| "", None ->
|
||||||
|
invalid_arg ""
|
||||||
|
| "", Some default_port -> string_of_int default_port
|
||||||
|
| port, _ -> port in
|
||||||
|
Lwt_utils.getaddrinfo ~passive ~node ~service
|
||||||
|
|
||||||
|
let resolve_addrs ?default_port ?passive peers =
|
||||||
|
Lwt_list.fold_left_s begin fun a peer ->
|
||||||
|
resolve_addr ?default_port ?passive peer >>= fun points ->
|
||||||
|
Lwt.return (List.rev_append points a)
|
||||||
|
end [] peers
|
||||||
|
|
||||||
|
let resolve_listening_addrs listen_addr =
|
||||||
|
resolve_addr
|
||||||
|
~default_port:default_net_port
|
||||||
|
~passive:true
|
||||||
|
listen_addr
|
||||||
|
|
||||||
|
let resolve_rpc_listening_addrs listen_addr =
|
||||||
|
resolve_addr
|
||||||
|
~default_port:default_rpc_port
|
||||||
|
~passive:true
|
||||||
|
listen_addr
|
||||||
|
|
||||||
|
let resolve_bootstrap_addrs peers =
|
||||||
|
resolve_addrs
|
||||||
|
~default_port:default_net_port
|
||||||
|
peers
|
||||||
|
let check_listening_addr config =
|
||||||
|
match config.net.listen_addr with
|
||||||
|
| None -> Lwt.return_unit
|
||||||
|
| Some addr ->
|
||||||
|
Lwt.catch begin fun () ->
|
||||||
|
resolve_listening_addrs addr >>= function
|
||||||
|
| [] ->
|
||||||
|
Format.eprintf "Warning: failed to resolve %S\n@." addr ;
|
||||||
|
Lwt.return_unit
|
||||||
|
| _ :: _ ->
|
||||||
|
Lwt.return_unit
|
||||||
|
end begin function
|
||||||
|
| (Invalid_argument msg) ->
|
||||||
|
Format.eprintf "Warning: failed to parse %S:\ %s\n@." addr msg ;
|
||||||
|
Lwt.return_unit
|
||||||
|
| exn -> Lwt.fail exn
|
||||||
|
end
|
||||||
|
|
||||||
|
let check_rpc_listening_addr config =
|
||||||
|
match config.rpc.listen_addr with
|
||||||
|
| None -> Lwt.return_unit
|
||||||
|
| Some addr ->
|
||||||
|
Lwt.catch begin fun () ->
|
||||||
|
resolve_rpc_listening_addrs addr >>= function
|
||||||
|
| [] ->
|
||||||
|
Format.eprintf "Warning: failed to resolve %S\n@." addr ;
|
||||||
|
Lwt.return_unit
|
||||||
|
| _ :: _ ->
|
||||||
|
Lwt.return_unit
|
||||||
|
end begin function
|
||||||
|
| (Invalid_argument msg) ->
|
||||||
|
Format.eprintf "Warning: failed to parse %S:\ %s\n@." addr msg ;
|
||||||
|
Lwt.return_unit
|
||||||
|
| exn -> Lwt.fail exn
|
||||||
|
end
|
||||||
|
|
||||||
|
let check_bootstrap_peer addr =
|
||||||
|
Lwt.catch begin fun () ->
|
||||||
|
resolve_bootstrap_addrs [addr] >>= function
|
||||||
|
| [] ->
|
||||||
|
Format.eprintf "Warning: cannot resolve %S\n@." addr ;
|
||||||
|
Lwt.return_unit
|
||||||
|
| _ :: _ ->
|
||||||
|
Lwt.return_unit
|
||||||
|
end begin function
|
||||||
|
| (Invalid_argument msg) ->
|
||||||
|
Format.eprintf "Warning: failed to parse %S:\ %s\n@." addr msg ;
|
||||||
|
Lwt.return_unit
|
||||||
|
| exn -> Lwt.fail exn
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
let check_bootstrap_peers config =
|
||||||
|
Lwt_list.iter_p check_bootstrap_peer config.net.bootstrap_peers
|
||||||
|
|
||||||
|
let check config =
|
||||||
|
check_listening_addr config >>= fun () ->
|
||||||
|
check_rpc_listening_addr config >>= fun () ->
|
||||||
|
check_bootstrap_peers config >>= fun () ->
|
||||||
|
Lwt.return_unit
|
78
src/node/main/node_config_file.mli
Normal file
78
src/node/main/node_config_file.mli
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
(**************************************************************************)
|
||||||
|
(* *)
|
||||||
|
(* Copyright (c) 2014 - 2016. *)
|
||||||
|
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
|
||||||
|
(* *)
|
||||||
|
(* All rights reserved. No warranty, explicit or implicit, provided. *)
|
||||||
|
(* *)
|
||||||
|
(**************************************************************************)
|
||||||
|
|
||||||
|
open P2p_types
|
||||||
|
|
||||||
|
type t = {
|
||||||
|
data_dir : string ;
|
||||||
|
net : net ;
|
||||||
|
rpc : rpc ;
|
||||||
|
log : log ;
|
||||||
|
}
|
||||||
|
|
||||||
|
and net = {
|
||||||
|
expected_pow : float ;
|
||||||
|
bootstrap_peers : string list ;
|
||||||
|
listen_addr : string option ;
|
||||||
|
closed : bool ;
|
||||||
|
limits : P2p.limits ;
|
||||||
|
}
|
||||||
|
|
||||||
|
and rpc = {
|
||||||
|
listen_addr : string option ;
|
||||||
|
cors_origins : string list ;
|
||||||
|
cors_headers : string list ;
|
||||||
|
tls : tls option ;
|
||||||
|
}
|
||||||
|
|
||||||
|
and tls = {
|
||||||
|
cert : string ;
|
||||||
|
key : string ;
|
||||||
|
}
|
||||||
|
|
||||||
|
and log = {
|
||||||
|
output : Logging.Output.t ;
|
||||||
|
default_level : Logging.level ;
|
||||||
|
rules : string option ;
|
||||||
|
template : Logging.template ;
|
||||||
|
}
|
||||||
|
|
||||||
|
val default_data_dir: string
|
||||||
|
val default_net_port: int
|
||||||
|
val default_rpc_port: int
|
||||||
|
val default_net: net
|
||||||
|
val default_config: t
|
||||||
|
|
||||||
|
val update:
|
||||||
|
?data_dir:string ->
|
||||||
|
?min_connections:int ->
|
||||||
|
?expected_connections:int ->
|
||||||
|
?max_connections:int ->
|
||||||
|
?max_download_speed:int ->
|
||||||
|
?max_upload_speed:int ->
|
||||||
|
?expected_pow:float ->
|
||||||
|
?bootstrap_peers:string list ->
|
||||||
|
?listen_addr:string ->
|
||||||
|
?rpc_listen_addr:string ->
|
||||||
|
?closed:bool ->
|
||||||
|
?cors_origins:string list ->
|
||||||
|
?cors_headers:string list ->
|
||||||
|
?rpc_tls:tls ->
|
||||||
|
?log_output:Logging.Output.t ->
|
||||||
|
t -> t
|
||||||
|
|
||||||
|
val to_string: t -> string
|
||||||
|
val read: string -> t tzresult Lwt.t
|
||||||
|
val write: string -> t -> unit tzresult Lwt.t
|
||||||
|
|
||||||
|
val resolve_listening_addrs: string -> (P2p_types.addr * int) list Lwt.t
|
||||||
|
val resolve_rpc_listening_addrs: string -> (P2p_types.addr * int) list Lwt.t
|
||||||
|
val resolve_bootstrap_addrs: string list -> (P2p_types.addr * int) list Lwt.t
|
||||||
|
|
||||||
|
val check: t -> unit Lwt.t
|
150
src/node/main/node_identity_command.ml
Normal file
150
src/node/main/node_identity_command.ml
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
(**************************************************************************)
|
||||||
|
(* *)
|
||||||
|
(* Copyright (c) 2014 - 2016. *)
|
||||||
|
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
|
||||||
|
(* *)
|
||||||
|
(* All rights reserved. No warranty, explicit or implicit, provided. *)
|
||||||
|
(* *)
|
||||||
|
(**************************************************************************)
|
||||||
|
|
||||||
|
let (//) = Filename.concat
|
||||||
|
|
||||||
|
(** Commands *)
|
||||||
|
|
||||||
|
let identity_file data_dir = data_dir // Node_identity_file.default_name
|
||||||
|
|
||||||
|
let show { Node_config_file.data_dir } =
|
||||||
|
Node_identity_file.read (identity_file data_dir) >>=? fun id ->
|
||||||
|
Format.printf "Gid: %a.@." P2p_types.Gid.pp id.gid ;
|
||||||
|
return ()
|
||||||
|
|
||||||
|
let generate { Node_config_file.data_dir ; net } =
|
||||||
|
let identity_file = identity_file data_dir in
|
||||||
|
if Sys.file_exists identity_file then
|
||||||
|
fail (Node_identity_file.Existent_identity_file identity_file)
|
||||||
|
else
|
||||||
|
let target = Crypto_box.make_target net.expected_pow in
|
||||||
|
Format.eprintf "Generating a new identity... " ;
|
||||||
|
let id =
|
||||||
|
P2p.Identity.generate_with_animation Format.err_formatter target in
|
||||||
|
Node_identity_file.write identity_file id >>=? fun () ->
|
||||||
|
Format.eprintf
|
||||||
|
"Stored the new identity (%a) into '%s'.@."
|
||||||
|
P2p.Gid.pp id.gid identity_file ;
|
||||||
|
return ()
|
||||||
|
|
||||||
|
let check { Node_config_file.data_dir ; net = { expected_pow } } =
|
||||||
|
Node_identity_file.read
|
||||||
|
~expected_pow (identity_file data_dir) >>=? fun id ->
|
||||||
|
Format.printf
|
||||||
|
"Gid: %a. Proof of work is higher than %.2f.@."
|
||||||
|
P2p_types.Gid.pp id.gid expected_pow ;
|
||||||
|
return ()
|
||||||
|
|
||||||
|
(** Main *)
|
||||||
|
|
||||||
|
module Term = struct
|
||||||
|
|
||||||
|
type subcommand = Show | Generate | Check
|
||||||
|
|
||||||
|
let process subcommand data_dir config_file expected_pow =
|
||||||
|
let res =
|
||||||
|
begin
|
||||||
|
match data_dir, config_file with
|
||||||
|
| None, None ->
|
||||||
|
return Node_config_file.default_config
|
||||||
|
| None, Some config_file ->
|
||||||
|
Node_config_file.read config_file
|
||||||
|
| Some data_dir, None ->
|
||||||
|
Node_config_file.read (data_dir // "config.json") >>=? fun cfg ->
|
||||||
|
return { cfg with data_dir }
|
||||||
|
| Some data_dir, Some config_file ->
|
||||||
|
Node_config_file.read config_file >>=? fun cfg ->
|
||||||
|
return { cfg with data_dir }
|
||||||
|
end >>=? fun cfg ->
|
||||||
|
let cfg = Node_config_file.update ?expected_pow cfg in
|
||||||
|
match subcommand with
|
||||||
|
| Show -> show cfg
|
||||||
|
| Generate -> generate cfg
|
||||||
|
| Check -> check cfg in
|
||||||
|
match Lwt_main.run res with
|
||||||
|
| Ok () -> `Ok ()
|
||||||
|
| Error err -> `Error (false, Format.asprintf "%a" pp_print_error err)
|
||||||
|
|
||||||
|
let subcommand_arg =
|
||||||
|
let parser = function
|
||||||
|
| "show" -> `Ok Show
|
||||||
|
| "generate" -> `Ok Generate
|
||||||
|
| "check" -> `Ok Check
|
||||||
|
| s -> `Error ("invalid argument: " ^ s)
|
||||||
|
and printer fmt = function
|
||||||
|
| Show -> Format.fprintf fmt "show"
|
||||||
|
| Generate -> Format.fprintf fmt "generate"
|
||||||
|
| Check -> Format.fprintf fmt "check" in
|
||||||
|
let doc =
|
||||||
|
"Operation to perform. \
|
||||||
|
Possible values: $(b,show), $(b,generate), $(b,check)." in
|
||||||
|
let open Cmdliner.Arg in
|
||||||
|
value & pos 0 (parser, printer) Show & info [] ~docv:"OPERATION" ~doc
|
||||||
|
|
||||||
|
let expected_pow =
|
||||||
|
let open Cmdliner in
|
||||||
|
let doc =
|
||||||
|
"Expected amount of proof-of-work for the node identity. \
|
||||||
|
The optional parameter should be a float between 0 and 256, where
|
||||||
|
0 disable the proof-of-work mechanism." in
|
||||||
|
Arg.(value & pos 1 (some float) None & info [] ~docv:"DIFFICULTY" ~doc)
|
||||||
|
|
||||||
|
let term =
|
||||||
|
Cmdliner.Term.(ret (const process
|
||||||
|
$ subcommand_arg
|
||||||
|
$ Node_shared_arg.Term.data_dir
|
||||||
|
$ Node_shared_arg.Term.config_file
|
||||||
|
$ expected_pow))
|
||||||
|
end
|
||||||
|
|
||||||
|
module Manpage = struct
|
||||||
|
|
||||||
|
let command_description =
|
||||||
|
"The $(b,identity) command is meant to create and manage node \
|
||||||
|
identities. An $(i,identity) uniquely identifies a peer on the \
|
||||||
|
network and consists of a cryptographic key pair as well as a \
|
||||||
|
proof-of-work stamp that certifies \
|
||||||
|
that enough CPU time has been dedicated to produce the identity, \
|
||||||
|
to avoid sybil attacks. An identity with enough proof-of-work is \
|
||||||
|
required to participate in the Tezos network, therefore this command \
|
||||||
|
is necessary to launch Tezos the first time."
|
||||||
|
|
||||||
|
let description = [
|
||||||
|
`S "DESCRIPTION" ;
|
||||||
|
`P (command_description ^ " Several options are possible:");
|
||||||
|
`P "$(b,show) reads, parses and displays the current identity of the node. \
|
||||||
|
Use this command to see what identity will be used by Tezos. \
|
||||||
|
This is the default operation." ;
|
||||||
|
`P "$(b,generate [difficulty]) generates an identity whose \
|
||||||
|
proof of work stamp difficulty is at least equal to $(i,difficulty). \
|
||||||
|
The value provided must be a floating point number between 0 and 128. \
|
||||||
|
It roughly reflects the numbers of expected leading zeroes in the hash \
|
||||||
|
of the identity data-structure. \
|
||||||
|
Therefore, a value of 0 means no proof-of-work, and the difficulty \
|
||||||
|
doubles for each increment of 1 in the difficulty value." ;
|
||||||
|
`P "$(b,check [difficulty]) checks that an identity is valid and that its \
|
||||||
|
proof of work stamp difficulty is at least equal to $(i,difficulty)." ;
|
||||||
|
]
|
||||||
|
|
||||||
|
let man =
|
||||||
|
description @
|
||||||
|
(* [ `S misc_docs ] @ *)
|
||||||
|
Node_shared_arg.Manpage.bugs
|
||||||
|
|
||||||
|
let info =
|
||||||
|
Cmdliner.Term.info
|
||||||
|
~doc: "Manage node identities"
|
||||||
|
~man
|
||||||
|
"identity"
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
let cmd =
|
||||||
|
Term.term, Manpage.info
|
||||||
|
|
14
src/node/main/node_identity_command.mli
Normal file
14
src/node/main/node_identity_command.mli
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
(**************************************************************************)
|
||||||
|
(* *)
|
||||||
|
(* Copyright (c) 2014 - 2016. *)
|
||||||
|
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
|
||||||
|
(* *)
|
||||||
|
(* All rights reserved. No warranty, explicit or implicit, provided. *)
|
||||||
|
(* *)
|
||||||
|
(**************************************************************************)
|
||||||
|
|
||||||
|
val cmd: unit Cmdliner.Term.t * Cmdliner.Term.info
|
||||||
|
|
||||||
|
module Manpage : sig
|
||||||
|
val command_description: string
|
||||||
|
end
|
86
src/node/main/node_identity_file.ml
Normal file
86
src/node/main/node_identity_file.ml
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
(**************************************************************************)
|
||||||
|
(* *)
|
||||||
|
(* Copyright (c) 2014 - 2016. *)
|
||||||
|
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
|
||||||
|
(* *)
|
||||||
|
(* All rights reserved. No warranty, explicit or implicit, provided. *)
|
||||||
|
(* *)
|
||||||
|
(**************************************************************************)
|
||||||
|
|
||||||
|
let default_name = "identity.json"
|
||||||
|
|
||||||
|
type error += No_identity_file of string
|
||||||
|
type error += Insufficient_proof_of_work of { expected: float }
|
||||||
|
|
||||||
|
let () =
|
||||||
|
register_error_kind
|
||||||
|
`Permanent
|
||||||
|
~id:"main.identity.no_file"
|
||||||
|
~title:"TODO"
|
||||||
|
~description:"TODO"
|
||||||
|
~pp:(fun ppf file ->
|
||||||
|
Format.fprintf ppf
|
||||||
|
"Cannot read the identity file: `%s`. \
|
||||||
|
See `%s identity --help` on how to generate an identity."
|
||||||
|
file Sys.argv.(0))
|
||||||
|
Data_encoding.(obj1 (req "file" string))
|
||||||
|
(function No_identity_file file -> Some file | _ -> None)
|
||||||
|
(fun file -> No_identity_file file)
|
||||||
|
|
||||||
|
let () =
|
||||||
|
register_error_kind
|
||||||
|
`Permanent
|
||||||
|
~id:"main.identity.insufficient_proof_of_work"
|
||||||
|
~title:"TODO"
|
||||||
|
~description:"TODO"
|
||||||
|
~pp:(fun ppf expected ->
|
||||||
|
Format.fprintf ppf
|
||||||
|
"The current identity does not embed a sufficient stamp of proof-of-work. \
|
||||||
|
(expected level: %.2f). \
|
||||||
|
See `%s identity --help` on how to generate a new identity."
|
||||||
|
expected Sys.argv.(0))
|
||||||
|
Data_encoding.(obj1 (req "expected" float))
|
||||||
|
(function Insufficient_proof_of_work { expected } -> Some expected | _ -> None)
|
||||||
|
(fun expected -> Insufficient_proof_of_work { expected })
|
||||||
|
|
||||||
|
let read ?expected_pow file =
|
||||||
|
Lwt_unix.file_exists file >>= function
|
||||||
|
| false ->
|
||||||
|
fail (No_identity_file file)
|
||||||
|
| true ->
|
||||||
|
Data_encoding_ezjsonm.read_file file >>=? fun json ->
|
||||||
|
let id = Data_encoding.Json.destruct P2p.Identity.encoding json in
|
||||||
|
match expected_pow with
|
||||||
|
| None -> return id
|
||||||
|
| Some expected ->
|
||||||
|
let target = Crypto_box.make_target expected in
|
||||||
|
if (Crypto_box.check_proof_of_work
|
||||||
|
id.public_key id.proof_of_work_stamp target) then
|
||||||
|
return id
|
||||||
|
else
|
||||||
|
fail (Insufficient_proof_of_work { expected })
|
||||||
|
|
||||||
|
type error += Existent_identity_file of string
|
||||||
|
|
||||||
|
let () =
|
||||||
|
register_error_kind
|
||||||
|
`Permanent
|
||||||
|
~id:"main.identity.existent_file"
|
||||||
|
~title:"TODO"
|
||||||
|
~description:"TODO"
|
||||||
|
~pp:(fun ppf file ->
|
||||||
|
Format.fprintf ppf
|
||||||
|
"Cannot implicitely overwrite the current identity file: '%s'. \
|
||||||
|
See `%s identity --help` on how to generate a new identity."
|
||||||
|
file Sys.argv.(0))
|
||||||
|
Data_encoding.(obj1 (req "file" string))
|
||||||
|
(function Existent_identity_file file -> Some file | _ -> None)
|
||||||
|
(fun file -> Existent_identity_file file)
|
||||||
|
|
||||||
|
let write file identity =
|
||||||
|
if Sys.file_exists file then
|
||||||
|
fail (Existent_identity_file file)
|
||||||
|
else
|
||||||
|
Lwt_utils.create_dir ~perm:0o700 (Filename.dirname file) >>= fun () ->
|
||||||
|
Data_encoding_ezjsonm.write_file file
|
||||||
|
(Data_encoding.Json.construct P2p.Identity.encoding identity)
|
21
src/node/main/node_identity_file.mli
Normal file
21
src/node/main/node_identity_file.mli
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
(**************************************************************************)
|
||||||
|
(* *)
|
||||||
|
(* Copyright (c) 2014 - 2016. *)
|
||||||
|
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
|
||||||
|
(* *)
|
||||||
|
(* All rights reserved. No warranty, explicit or implicit, provided. *)
|
||||||
|
(* *)
|
||||||
|
(**************************************************************************)
|
||||||
|
|
||||||
|
val default_name: string
|
||||||
|
|
||||||
|
type error += No_identity_file of string
|
||||||
|
type error += Insufficient_proof_of_work of { expected: float }
|
||||||
|
|
||||||
|
val read:
|
||||||
|
?expected_pow:float ->
|
||||||
|
string -> P2p.Identity.t tzresult Lwt.t
|
||||||
|
|
||||||
|
type error += Existent_identity_file of string
|
||||||
|
|
||||||
|
val write: string -> P2p.Identity.t -> unit tzresult Lwt.t
|
285
src/node/main/node_run_command.ml
Normal file
285
src/node/main/node_run_command.ml
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
(**************************************************************************)
|
||||||
|
(* *)
|
||||||
|
(* Copyright (c) 2014 - 2016. *)
|
||||||
|
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
|
||||||
|
(* *)
|
||||||
|
(* All rights reserved. No warranty, explicit or implicit, provided. *)
|
||||||
|
(* *)
|
||||||
|
(**************************************************************************)
|
||||||
|
|
||||||
|
open Logging.Node.Main
|
||||||
|
|
||||||
|
let genesis = {
|
||||||
|
Store.time =
|
||||||
|
Time.of_notation_exn "2016-11-01T00:00:00Z" ;
|
||||||
|
block =
|
||||||
|
Block_hash.of_b48check
|
||||||
|
"grHGHkVfgJb5gPaRd5AtQsa65g9GyLcXgQsHbSnQ5SD5DEp2ctqck" ;
|
||||||
|
protocol =
|
||||||
|
Protocol_hash.of_b48check
|
||||||
|
"4p64VagsbXchSF88eaPy5XrkqMLEjBCaSnaGv2vQkhv8e37Nnqmrd" ;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 test_protocol = None
|
||||||
|
|
||||||
|
let init_logger ?verbosity (log_config : Node_config_file.log) =
|
||||||
|
let open Logging in
|
||||||
|
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 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 Updater.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 ->
|
||||||
|
begin
|
||||||
|
match sandbox with
|
||||||
|
| Some _ -> return None
|
||||||
|
| None ->
|
||||||
|
Node_identity_file.read
|
||||||
|
(config.data_dir //
|
||||||
|
Node_identity_file.default_name) >>=? fun identity ->
|
||||||
|
lwt_log_notice
|
||||||
|
"Peer's global id: %a"
|
||||||
|
P2p.Gid.pp identity.gid >>= fun () ->
|
||||||
|
(* TODO "WARN" when pow is below our expectation. *)
|
||||||
|
begin
|
||||||
|
match config.net.listen_addr with
|
||||||
|
| None ->
|
||||||
|
lwt_log_notice "Not listening to RPC calls." >>= fun () ->
|
||||||
|
return (None, None)
|
||||||
|
| Some addr ->
|
||||||
|
Node_config_file.resolve_listening_addrs addr >>= function
|
||||||
|
| [] ->
|
||||||
|
failwith "Cannot resolve RPC listening address: %S" addr
|
||||||
|
| (addr, port) :: _ -> return (Some addr, Some port)
|
||||||
|
end >>=? fun (listening_addr, listening_port) ->
|
||||||
|
Node_config_file.resolve_bootstrap_addrs
|
||||||
|
config.net.bootstrap_peers >>= fun trusted_points ->
|
||||||
|
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 ;
|
||||||
|
test_protocol ;
|
||||||
|
patch_context ;
|
||||||
|
store_root = store_dir config.data_dir ;
|
||||||
|
context_root = context_dir config.data_dir ;
|
||||||
|
p2p = p2p_config ;
|
||||||
|
} in
|
||||||
|
Node.create node_config
|
||||||
|
|
||||||
|
let () =
|
||||||
|
let old_hook = !Lwt.async_exception_hook in
|
||||||
|
Lwt.async_exception_hook := function
|
||||||
|
| Ssl.Read_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 id = try Lwt_exit.exit id with _ -> () in
|
||||||
|
ignore (Lwt_unix.on_signal Sys.sigint handler : Lwt_unix.signal_handler_id)
|
||||||
|
|
||||||
|
let run ?verbosity ?sandbox (config : Node_config_file.t) =
|
||||||
|
Lwt_utils.create_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 RPC_server.shutdown rpc >>= fun () ->
|
||||||
|
lwt_log_notice "BYE (%d)" x >>= 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 args >>=? fun config ->
|
||||||
|
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 is disabled, and constants of \
|
||||||
|
the economical 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='<section> -> <level> [ ; ...]') \
|
||||||
|
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 the most prioritary one."
|
||||||
|
) ;
|
||||||
|
]
|
||||||
|
|
||||||
|
let examples =
|
||||||
|
[
|
||||||
|
`S "EXAMPLES" ;
|
||||||
|
`I ("$(b,Run in sandbox mode listening to RPC commands \
|
||||||
|
at localhost port 8732)",
|
||||||
|
"$(mname) run --sandbox --base-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
|
15
src/node/main/node_run_command.mli
Normal file
15
src/node/main/node_run_command.mli
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
(**************************************************************************)
|
||||||
|
(* *)
|
||||||
|
(* Copyright (c) 2014 - 2016. *)
|
||||||
|
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
|
||||||
|
(* *)
|
||||||
|
(* All rights reserved. No warranty, explicit or implicit, provided. *)
|
||||||
|
(* *)
|
||||||
|
(**************************************************************************)
|
||||||
|
|
||||||
|
val cmd: unit Cmdliner.Term.t * Cmdliner.Term.info
|
||||||
|
|
||||||
|
module Manpage : sig
|
||||||
|
val command_description: string
|
||||||
|
val examples: Cmdliner.Manpage.block list
|
||||||
|
end
|
250
src/node/main/node_shared_arg.ml
Normal file
250
src/node/main/node_shared_arg.ml
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
(**************************************************************************)
|
||||||
|
(* *)
|
||||||
|
(* Copyright (c) 2014 - 2016. *)
|
||||||
|
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
|
||||||
|
(* *)
|
||||||
|
(* All rights reserved. No warranty, explicit or implicit, provided. *)
|
||||||
|
(* *)
|
||||||
|
(**************************************************************************)
|
||||||
|
|
||||||
|
open Cmdliner
|
||||||
|
open P2p_types
|
||||||
|
|
||||||
|
let (//) = Filename.concat
|
||||||
|
|
||||||
|
type t = {
|
||||||
|
data_dir: string option ;
|
||||||
|
config_file: string ;
|
||||||
|
min_connections: int option ;
|
||||||
|
expected_connections: int option ;
|
||||||
|
max_connections: int option ;
|
||||||
|
max_download_speed: int option ;
|
||||||
|
max_upload_speed: int option ;
|
||||||
|
expected_pow: float option ;
|
||||||
|
peers: string list ;
|
||||||
|
no_bootstrap_peers: bool ;
|
||||||
|
listen_addr: string option ;
|
||||||
|
rpc_listen_addr: string option ;
|
||||||
|
closed: bool ;
|
||||||
|
cors_origins: string list ;
|
||||||
|
cors_headers: string list ;
|
||||||
|
rpc_tls: Node_config_file.tls option ;
|
||||||
|
log_output: Logging.Output.t option ;
|
||||||
|
}
|
||||||
|
|
||||||
|
let wrap
|
||||||
|
data_dir config_file
|
||||||
|
connections max_download_speed max_upload_speed
|
||||||
|
listen_addr peers no_bootstrap_peers closed expected_pow
|
||||||
|
rpc_listen_addr rpc_tls
|
||||||
|
cors_origins cors_headers log_output =
|
||||||
|
|
||||||
|
let actual_data_dir =
|
||||||
|
Utils.unopt ~default:Node_config_file.default_data_dir data_dir in
|
||||||
|
|
||||||
|
let config_file =
|
||||||
|
Utils.unopt ~default:(actual_data_dir // "config.json") config_file in
|
||||||
|
|
||||||
|
let rpc_tls =
|
||||||
|
Utils.map_option
|
||||||
|
(fun (cert, key) -> { Node_config_file.cert ; key })
|
||||||
|
rpc_tls in
|
||||||
|
|
||||||
|
(* when `--expected-connections` is used,
|
||||||
|
override all the bounds defined in the configuration file. *)
|
||||||
|
let min_connections, expected_connections, max_connections =
|
||||||
|
match connections with
|
||||||
|
| None -> None, None, None
|
||||||
|
| Some x -> Some (x/2), Some x, Some (3*x/2) in
|
||||||
|
|
||||||
|
{ data_dir ;
|
||||||
|
config_file ;
|
||||||
|
min_connections ;
|
||||||
|
expected_connections ;
|
||||||
|
max_connections ;
|
||||||
|
max_download_speed ;
|
||||||
|
max_upload_speed ;
|
||||||
|
expected_pow ;
|
||||||
|
peers ;
|
||||||
|
no_bootstrap_peers ;
|
||||||
|
listen_addr ;
|
||||||
|
rpc_listen_addr ;
|
||||||
|
closed ;
|
||||||
|
cors_origins ;
|
||||||
|
cors_headers ;
|
||||||
|
rpc_tls ;
|
||||||
|
log_output ;
|
||||||
|
}
|
||||||
|
|
||||||
|
module Manpage = struct
|
||||||
|
|
||||||
|
let misc_section = "MISC OPTIONS"
|
||||||
|
let network_section = "NETWORK OPTIONS"
|
||||||
|
let rpc_section = "RPC OPTIONS"
|
||||||
|
|
||||||
|
let args = [
|
||||||
|
`S network_section ;
|
||||||
|
`S rpc_section ;
|
||||||
|
`S misc_section ;
|
||||||
|
]
|
||||||
|
|
||||||
|
let bugs = [
|
||||||
|
`S "BUGS";
|
||||||
|
`P "Check bug reports at https://github.com/tezos/tezos/issues.";
|
||||||
|
]
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
module Term = struct
|
||||||
|
|
||||||
|
let log_output_converter =
|
||||||
|
(fun s -> match Logging.Output.of_string s with
|
||||||
|
| Some res -> `Ok res
|
||||||
|
| None -> `Error s),
|
||||||
|
Logging.Output.pp
|
||||||
|
|
||||||
|
(* misc args *)
|
||||||
|
|
||||||
|
let docs = Manpage.misc_section
|
||||||
|
|
||||||
|
let log_output =
|
||||||
|
let doc =
|
||||||
|
"Log output. Either $(i,stdout), $(i,stderr), \
|
||||||
|
$(i,syslog:<facility>) or a file path." in
|
||||||
|
Arg.(value & opt (some log_output_converter) None &
|
||||||
|
info ~docs ~docv:"OUTPUT" ~doc ["log-output"])
|
||||||
|
|
||||||
|
let data_dir =
|
||||||
|
let doc =
|
||||||
|
"The directory where the Tezos node will store all its data." in
|
||||||
|
Arg.(value & opt (some string) None &
|
||||||
|
info ~docs ~doc ~docv:"DIR" ["data-dir"])
|
||||||
|
|
||||||
|
let config_file =
|
||||||
|
let doc = "The main configuration file." in
|
||||||
|
Arg.(value & opt (some string) None &
|
||||||
|
info ~docs ~doc ~docv:"FILE" ["config-file"])
|
||||||
|
|
||||||
|
(* net args *)
|
||||||
|
|
||||||
|
let docs = Manpage.network_section
|
||||||
|
|
||||||
|
let connections =
|
||||||
|
let doc =
|
||||||
|
"The number of running connections that we aim for." in
|
||||||
|
Arg.(value & opt (some int) None &
|
||||||
|
info ~docs ~doc ~docv:"NUM" ["connections"])
|
||||||
|
|
||||||
|
let max_download_speed =
|
||||||
|
let doc =
|
||||||
|
"The maximum number of bytes read per second." in
|
||||||
|
Arg.(value & opt (some int) None &
|
||||||
|
info ~docs ~doc ~docv:"NUM" ["max-download-speed"])
|
||||||
|
|
||||||
|
let max_upload_speed =
|
||||||
|
let doc =
|
||||||
|
"The maximum number of bytes sent per second." in
|
||||||
|
Arg.(value & opt (some int) None &
|
||||||
|
info ~docs ~doc ~docv:"NUM" ["max-upload-speed"])
|
||||||
|
|
||||||
|
let listen_addr =
|
||||||
|
let doc =
|
||||||
|
"The TCP address and port at which this instance can be reached." in
|
||||||
|
Arg.(value & opt (some string) None &
|
||||||
|
info ~docs ~doc ~docv:"ADDR:PORT" ["net-addr"])
|
||||||
|
|
||||||
|
let no_bootstrap_peers =
|
||||||
|
let doc =
|
||||||
|
"Ignore the peers foud in the config file (or the hardcoded \
|
||||||
|
bootstrap peers in the absence of config file)." in
|
||||||
|
Arg.(value & flag &
|
||||||
|
info ~docs ~doc ["no-bootstrap-peers"])
|
||||||
|
|
||||||
|
let peers =
|
||||||
|
let doc =
|
||||||
|
"A peer to bootstrap the network from. \
|
||||||
|
Can be used several times to add several peers." in
|
||||||
|
Arg.(value & opt_all string [] &
|
||||||
|
info ~docs ~doc ~docv:"ADDR:PORT" ["peer"])
|
||||||
|
|
||||||
|
let expected_pow =
|
||||||
|
let doc =
|
||||||
|
"Expected level of proof-of-work for peers identity." in
|
||||||
|
Arg.(value & opt (some float) None &
|
||||||
|
info ~docs ~doc ~docv:"FLOAT" ["expected-pow"])
|
||||||
|
|
||||||
|
let closed =
|
||||||
|
let doc =
|
||||||
|
"Only accept connections from the configured bootstrap peers." in
|
||||||
|
Arg.(value & flag & info ~docs ~doc ["closed"])
|
||||||
|
|
||||||
|
(* rpc args *)
|
||||||
|
let docs = Manpage.rpc_section
|
||||||
|
|
||||||
|
let rpc_listen_addr =
|
||||||
|
let doc =
|
||||||
|
"The TCP socket address at which this RPC server \
|
||||||
|
instance can be reached." in
|
||||||
|
Arg.(value & opt (some string) None &
|
||||||
|
info ~docs ~doc ~docv:"ADDR:PORT" ["rpc-addr"])
|
||||||
|
|
||||||
|
let rpc_tls =
|
||||||
|
let doc =
|
||||||
|
"Enable TLS for this RPC server \
|
||||||
|
with the provided certificate and key." in
|
||||||
|
Arg.(value & opt (some (pair string string)) None &
|
||||||
|
info ~docs ~doc ~docv:"crt,key" ["rpc-tls"])
|
||||||
|
|
||||||
|
let cors_origins =
|
||||||
|
let doc =
|
||||||
|
"CORS origin allowed by the RPC server \
|
||||||
|
via Access-Control-Allow-Origin; may be used multiple times" in
|
||||||
|
Arg.(value & opt_all string [] &
|
||||||
|
info ~docs ~doc ~docv:"ORIGIN" ["cors-origin"])
|
||||||
|
|
||||||
|
let cors_headers =
|
||||||
|
let doc =
|
||||||
|
"Header reported by Access-Control-Allow-Headers \
|
||||||
|
reported during CORS preflighting; may be used multiple times" in
|
||||||
|
Arg.(value & opt_all string [] &
|
||||||
|
info ~docs ~doc ~docv:"HEADER" ["cors-header"])
|
||||||
|
|
||||||
|
let args =
|
||||||
|
let open Term in
|
||||||
|
const wrap $ data_dir $ config_file
|
||||||
|
$ connections
|
||||||
|
$ max_download_speed $ max_upload_speed
|
||||||
|
$ listen_addr $ peers $ no_bootstrap_peers $ closed $ expected_pow
|
||||||
|
$ rpc_listen_addr $ rpc_tls
|
||||||
|
$ cors_origins $ cors_headers
|
||||||
|
$ log_output
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
let read_and_patch_config_file args =
|
||||||
|
begin
|
||||||
|
if Sys.file_exists args.config_file then
|
||||||
|
Node_config_file.read args.config_file
|
||||||
|
else
|
||||||
|
return Node_config_file.default_config
|
||||||
|
end >>=? fun cfg ->
|
||||||
|
let { data_dir ;
|
||||||
|
min_connections ; expected_connections ; max_connections ;
|
||||||
|
max_download_speed ; max_upload_speed ;
|
||||||
|
expected_pow ;
|
||||||
|
peers ; no_bootstrap_peers ;
|
||||||
|
listen_addr ; closed ;
|
||||||
|
rpc_listen_addr ; rpc_tls ;
|
||||||
|
cors_origins ; cors_headers ;
|
||||||
|
log_output } = args in
|
||||||
|
let bootstrap_peers =
|
||||||
|
if no_bootstrap_peers then
|
||||||
|
peers
|
||||||
|
else
|
||||||
|
cfg.net.bootstrap_peers @ peers in
|
||||||
|
return @@
|
||||||
|
Node_config_file.update
|
||||||
|
?data_dir ?min_connections ?expected_connections ?max_connections
|
||||||
|
?max_download_speed ?max_upload_speed ?expected_pow
|
||||||
|
~bootstrap_peers ?listen_addr ?rpc_listen_addr
|
||||||
|
~closed ~cors_origins ~cors_headers ?rpc_tls ?log_output cfg
|
44
src/node/main/node_shared_arg.mli
Normal file
44
src/node/main/node_shared_arg.mli
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
(**************************************************************************)
|
||||||
|
(* *)
|
||||||
|
(* Copyright (c) 2014 - 2016. *)
|
||||||
|
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
|
||||||
|
(* *)
|
||||||
|
(* All rights reserved. No warranty, explicit or implicit, provided. *)
|
||||||
|
(* *)
|
||||||
|
(**************************************************************************)
|
||||||
|
|
||||||
|
open P2p_types
|
||||||
|
|
||||||
|
type t = {
|
||||||
|
data_dir: string option ;
|
||||||
|
config_file: string ;
|
||||||
|
min_connections: int option ;
|
||||||
|
expected_connections: int option ;
|
||||||
|
max_connections: int option ;
|
||||||
|
max_download_speed: int option ;
|
||||||
|
max_upload_speed: int option ;
|
||||||
|
expected_pow: float option ;
|
||||||
|
peers: string list ;
|
||||||
|
no_bootstrap_peers: bool ;
|
||||||
|
listen_addr: string option ;
|
||||||
|
rpc_listen_addr: string option ;
|
||||||
|
closed: bool ;
|
||||||
|
cors_origins: string list ;
|
||||||
|
cors_headers: string list ;
|
||||||
|
rpc_tls: Node_config_file.tls option ;
|
||||||
|
log_output: Logging.Output.t option ;
|
||||||
|
}
|
||||||
|
|
||||||
|
module Term : sig
|
||||||
|
val args: t Cmdliner.Term.t
|
||||||
|
val data_dir: string option Cmdliner.Term.t
|
||||||
|
val config_file: string option Cmdliner.Term.t
|
||||||
|
end
|
||||||
|
|
||||||
|
val read_and_patch_config_file: t -> Node_config_file.t tzresult Lwt.t
|
||||||
|
|
||||||
|
module Manpage : sig
|
||||||
|
val misc_section: string
|
||||||
|
val args: Cmdliner.Manpage.block list
|
||||||
|
val bugs: Cmdliner.Manpage.block list
|
||||||
|
end
|
@ -29,8 +29,8 @@ type 'msg message_config = 'msg P2p_connection_pool.message_config = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type config = {
|
type config = {
|
||||||
listening_port : port option ;
|
listening_port : port option;
|
||||||
listening_addr : addr option ;
|
listening_addr : addr option;
|
||||||
trusted_points : Point.t list ;
|
trusted_points : Point.t list ;
|
||||||
peers_file : string ;
|
peers_file : string ;
|
||||||
closed_network : bool ;
|
closed_network : bool ;
|
||||||
@ -62,10 +62,14 @@ type limits = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let create_scheduler limits =
|
let create_scheduler limits =
|
||||||
|
let max_upload_speed =
|
||||||
|
map_option limits.max_upload_speed ~f:(( * ) 1024) in
|
||||||
|
let max_download_speed =
|
||||||
|
map_option limits.max_upload_speed ~f:(( * ) 1024) in
|
||||||
P2p_io_scheduler.create
|
P2p_io_scheduler.create
|
||||||
~read_buffer_size:limits.read_buffer_size
|
~read_buffer_size:limits.read_buffer_size
|
||||||
?max_upload_speed:limits.max_upload_speed
|
?max_upload_speed
|
||||||
?max_download_speed:limits.max_download_speed
|
?max_download_speed
|
||||||
?read_queue_size:limits.read_queue_size
|
?read_queue_size:limits.read_queue_size
|
||||||
?write_queue_size:limits.write_queue_size
|
?write_queue_size:limits.write_queue_size
|
||||||
()
|
()
|
||||||
@ -123,7 +127,8 @@ let may_create_welcome_worker config limits pool =
|
|||||||
| Some port ->
|
| Some port ->
|
||||||
P2p_welcome.run
|
P2p_welcome.run
|
||||||
~backlog:limits.backlog pool
|
~backlog:limits.backlog pool
|
||||||
?addr:config.listening_addr port >>= fun w ->
|
?addr:config.listening_addr
|
||||||
|
port >>= fun w ->
|
||||||
Lwt.return (Some w)
|
Lwt.return (Some w)
|
||||||
|
|
||||||
type ('msg, 'meta) connection = ('msg, 'meta) P2p_connection_pool.connection
|
type ('msg, 'meta) connection = ('msg, 'meta) P2p_connection_pool.connection
|
||||||
|
@ -87,7 +87,7 @@ type limits = {
|
|||||||
(** Strict minimum number of connections (triggers an urgent maintenance) *)
|
(** Strict minimum number of connections (triggers an urgent maintenance) *)
|
||||||
|
|
||||||
expected_connections : int ;
|
expected_connections : int ;
|
||||||
(** Targeted number of connections to reach when bootstraping / maitening *)
|
(** Targeted number of connections to reach when bootstraping / maintaining *)
|
||||||
|
|
||||||
max_connections : int ;
|
max_connections : int ;
|
||||||
(** Maximum number of connections (exceeding peers are disconnected) *)
|
(** Maximum number of connections (exceeding peers are disconnected) *)
|
||||||
@ -96,7 +96,7 @@ type limits = {
|
|||||||
(** Argument of [Lwt_unix.accept].*)
|
(** Argument of [Lwt_unix.accept].*)
|
||||||
|
|
||||||
max_incoming_connections : int ;
|
max_incoming_connections : int ;
|
||||||
(** Maximum not-yet-authentified incoming connections. *)
|
(** Maximum not-yet-authenticated incoming connections. *)
|
||||||
|
|
||||||
max_download_speed : int option ;
|
max_download_speed : int option ;
|
||||||
(** Hard-limit in the number of bytes received per second. *)
|
(** Hard-limit in the number of bytes received per second. *)
|
||||||
|
@ -83,7 +83,8 @@ type port = int
|
|||||||
module Point = struct
|
module Point = struct
|
||||||
|
|
||||||
module T = struct
|
module T = struct
|
||||||
(* A net point (address x port). *)
|
|
||||||
|
(* A net point (address x port). *)
|
||||||
type t = addr * port
|
type t = addr * port
|
||||||
let compare (a1, p1) (a2, p2) =
|
let compare (a1, p1) (a2, p2) =
|
||||||
match Ipaddr.V6.compare a1 a2 with
|
match Ipaddr.V6.compare a1 a2 with
|
||||||
@ -92,7 +93,11 @@ module Point = struct
|
|||||||
let equal p1 p2 = compare p1 p2 = 0
|
let equal p1 p2 = compare p1 p2 = 0
|
||||||
let hash = Hashtbl.hash
|
let hash = Hashtbl.hash
|
||||||
let pp ppf (addr, port) =
|
let pp ppf (addr, port) =
|
||||||
Format.fprintf ppf "[%a]:%d" Ipaddr.V6.pp_hum addr port
|
match Ipaddr.v4_of_v6 addr with
|
||||||
|
| Some addr ->
|
||||||
|
Format.fprintf ppf "%a:%d" Ipaddr.V4.pp_hum addr port
|
||||||
|
| None ->
|
||||||
|
Format.fprintf ppf "[%a]:%d" Ipaddr.V6.pp_hum addr port
|
||||||
let pp_opt ppf = function
|
let pp_opt ppf = function
|
||||||
| None -> Format.pp_print_string ppf "none"
|
| None -> Format.pp_print_string ppf "none"
|
||||||
| Some point -> pp ppf point
|
| Some point -> pp ppf point
|
||||||
@ -100,16 +105,29 @@ module Point = struct
|
|||||||
let is_local (addr, _) = Ipaddr.V6.is_private addr
|
let is_local (addr, _) = Ipaddr.V6.is_private addr
|
||||||
let is_global (addr, _) = not @@ Ipaddr.V6.is_private addr
|
let is_global (addr, _) = not @@ Ipaddr.V6.is_private addr
|
||||||
|
|
||||||
let to_sockaddr (addr, port) = Unix.(ADDR_INET (Ipaddr_unix.V6.to_inet_addr addr, port))
|
let of_string str =
|
||||||
|
match String.rindex str ':' with
|
||||||
|
| exception Not_found -> `Error "not a valid node address (ip:port)"
|
||||||
|
| pos ->
|
||||||
|
let len = String.length str in
|
||||||
|
let addr, port =
|
||||||
|
String.sub str 0 pos, String.sub str (pos+1) (len - pos - 1) in
|
||||||
|
let addr = if addr = "" || addr = "_" then "[::]" else addr in
|
||||||
|
match Ipaddr.of_string_exn addr, int_of_string port with
|
||||||
|
| exception Failure _ -> `Error "not a valid node address (ip:port)"
|
||||||
|
| V4 ipv4, port -> `Ok (Ipaddr.v6_of_v4 ipv4, port)
|
||||||
|
| V6 ipv6, port -> `Ok (ipv6, port)
|
||||||
|
|
||||||
|
let of_string_exn str =
|
||||||
|
match of_string str with
|
||||||
|
| `Ok saddr -> saddr
|
||||||
|
| `Error msg -> invalid_arg msg
|
||||||
|
|
||||||
|
let to_string saddr = Format.asprintf "%a" pp saddr
|
||||||
|
|
||||||
let encoding =
|
let encoding =
|
||||||
let open Data_encoding in
|
Data_encoding.conv to_string of_string_exn Data_encoding.string
|
||||||
conv
|
|
||||||
(fun (addr, port) -> Ipaddr.V6.to_string addr, port)
|
|
||||||
(fun (addr, port) -> Ipaddr.V6.of_string_exn addr, port)
|
|
||||||
(obj2
|
|
||||||
(req "addr" string)
|
|
||||||
(req "port" int16))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
include T
|
include T
|
||||||
@ -127,6 +145,7 @@ end
|
|||||||
module Id_point = struct
|
module Id_point = struct
|
||||||
|
|
||||||
module T = struct
|
module T = struct
|
||||||
|
|
||||||
(* A net point (address x port). *)
|
(* A net point (address x port). *)
|
||||||
type t = addr * port option
|
type t = addr * port option
|
||||||
let empty = Ipaddr.V6.unspecified, None
|
let empty = Ipaddr.V6.unspecified, None
|
||||||
@ -157,6 +176,7 @@ module Id_point = struct
|
|||||||
(obj2
|
(obj2
|
||||||
(req "addr" string)
|
(req "addr" string)
|
||||||
(opt "port" int16))
|
(opt "port" int16))
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
include T
|
include T
|
||||||
|
@ -50,10 +50,10 @@ module Point : sig
|
|||||||
val compare : t -> t -> int
|
val compare : t -> t -> int
|
||||||
val pp : Format.formatter -> t -> unit
|
val pp : Format.formatter -> t -> unit
|
||||||
val pp_opt : Format.formatter -> t option -> unit
|
val pp_opt : Format.formatter -> t option -> unit
|
||||||
|
val of_string : string -> [> `Error of string | `Ok of addr * port ]
|
||||||
val encoding : t Data_encoding.t
|
val encoding : t Data_encoding.t
|
||||||
val is_local : t -> bool
|
val is_local : t -> bool
|
||||||
val is_global : t -> bool
|
val is_global : t -> bool
|
||||||
val to_sockaddr : t -> Unix.sockaddr
|
|
||||||
module Map : Map.S with type key = t
|
module Map : Map.S with type key = t
|
||||||
module Set : Set.S with type elt = t
|
module Set : Set.S with type elt = t
|
||||||
module Table : Hashtbl.S with type key = t
|
module Table : Hashtbl.S with type key = t
|
||||||
|
@ -44,11 +44,12 @@ let create_listening_socket ~backlog ?(addr = Ipaddr.V6.unspecified) port =
|
|||||||
let main_socket = Lwt_unix.(socket PF_INET6 SOCK_STREAM 0) in
|
let main_socket = Lwt_unix.(socket PF_INET6 SOCK_STREAM 0) in
|
||||||
Lwt_unix.(setsockopt main_socket SO_REUSEADDR true) ;
|
Lwt_unix.(setsockopt main_socket SO_REUSEADDR true) ;
|
||||||
Lwt_unix.Versioned.bind_2
|
Lwt_unix.Versioned.bind_2
|
||||||
main_socket (Point.to_sockaddr (addr, port)) >>= fun () ->
|
main_socket
|
||||||
|
Unix.(ADDR_INET (Ipaddr_unix.V6.to_inet_addr addr, port)) >>= fun () ->
|
||||||
Lwt_unix.listen main_socket backlog ;
|
Lwt_unix.listen main_socket backlog ;
|
||||||
Lwt.return main_socket
|
Lwt.return main_socket
|
||||||
|
|
||||||
let run ~backlog pool ?addr port =
|
let run ~backlog pool ?addr port =
|
||||||
Lwt.catch begin fun () ->
|
Lwt.catch begin fun () ->
|
||||||
create_listening_socket
|
create_listening_socket
|
||||||
~backlog ?addr port >>= fun socket ->
|
~backlog ?addr port >>= fun socket ->
|
||||||
|
@ -124,14 +124,14 @@ let get hash =
|
|||||||
|
|
||||||
(** Compiler *)
|
(** Compiler *)
|
||||||
|
|
||||||
let basedir = ref None
|
let datadir = ref None
|
||||||
let get_basedir () =
|
let get_datadir () =
|
||||||
match !basedir with
|
match !datadir with
|
||||||
| None -> fatal_error "not initialized"
|
| None -> fatal_error "not initialized"
|
||||||
| Some m -> m
|
| Some m -> m
|
||||||
|
|
||||||
let init dir =
|
let init dir =
|
||||||
basedir := Some dir
|
datadir := Some dir
|
||||||
|
|
||||||
type component = Tezos_compiler.Protocol.component = {
|
type component = Tezos_compiler.Protocol.component = {
|
||||||
name : string ;
|
name : string ;
|
||||||
@ -164,10 +164,10 @@ let extract dirname hash units =
|
|||||||
(List.map (fun {name} -> String.capitalize_ascii name) units)
|
(List.map (fun {name} -> String.capitalize_ascii name) units)
|
||||||
|
|
||||||
let do_compile hash units =
|
let do_compile hash units =
|
||||||
let basedir = get_basedir () in
|
let datadir = get_datadir () in
|
||||||
let source_dir = basedir // Protocol_hash.to_short_b48check hash // "src" in
|
let source_dir = datadir // Protocol_hash.to_short_b48check hash // "src" in
|
||||||
let log_file = basedir // Protocol_hash.to_short_b48check hash // "LOG" in
|
let log_file = datadir // Protocol_hash.to_short_b48check hash // "LOG" in
|
||||||
let plugin_file = basedir // Protocol_hash.to_short_b48check hash //
|
let plugin_file = datadir // Protocol_hash.to_short_b48check hash //
|
||||||
Format.asprintf "protocol_%a.cmxs" Protocol_hash.pp hash
|
Format.asprintf "protocol_%a.cmxs" Protocol_hash.pp hash
|
||||||
in
|
in
|
||||||
create_files source_dir units >>= fun _files ->
|
create_files source_dir units >>= fun _files ->
|
||||||
|
861
src/node_main.ml
861
src/node_main.ml
@ -7,838 +7,37 @@
|
|||||||
(* *)
|
(* *)
|
||||||
(**************************************************************************)
|
(**************************************************************************)
|
||||||
|
|
||||||
module V6 = Ipaddr.V6
|
let term =
|
||||||
|
let open Cmdliner.Term in
|
||||||
open Hash
|
ret (const (`Help (`Pager, None)))
|
||||||
open Error_monad
|
|
||||||
open Logging.Node.Main
|
let description = [
|
||||||
|
`S "DESCRIPTION" ;
|
||||||
let (//) = Filename.concat
|
`P "Entry point for initializing, configuring and running a Tezos node." ;
|
||||||
|
`P Node_identity_command.Manpage.command_description ;
|
||||||
let home =
|
`P Node_run_command.Manpage.command_description ;
|
||||||
try Sys.getenv "HOME"
|
`P Node_config_command.Manpage.command_description ;
|
||||||
with Not_found -> "/root"
|
]
|
||||||
|
|
||||||
let default_base_dir = home // ".tezos-node"
|
let man =
|
||||||
|
description @
|
||||||
let genesis_block =
|
Node_run_command.Manpage.examples
|
||||||
Block_hash.of_b48check
|
|
||||||
"grHGHkVfgJb5gPaRd5AtQsa65g9GyLcXgQsHbSnQ5SD5DEp2ctqck"
|
let info =
|
||||||
|
Cmdliner.Term.info
|
||||||
let genesis_protocol =
|
~doc:"The Tezos node"
|
||||||
Protocol_hash.of_b48check
|
~man
|
||||||
"4p64VagsbXchSF88eaPy5XrkqMLEjBCaSnaGv2vQkhv8e37Nnqmrd"
|
"tezos-node"
|
||||||
|
|
||||||
let test_protocol =
|
let commands = [
|
||||||
Some (Protocol_hash.of_b48check
|
Node_run_command.cmd ;
|
||||||
"2gagsSEvTKAHRjxAamgSdBNkv39VtNCqpaDXrrH4K8R4KQAAHrhe3")
|
Node_config_command.cmd ;
|
||||||
|
Node_identity_command.cmd ;
|
||||||
let genesis_time =
|
]
|
||||||
Time.of_notation_exn "2016-11-01T00:00:00Z"
|
|
||||||
|
|
||||||
let genesis = {
|
|
||||||
Store.time = genesis_time ;
|
|
||||||
block = genesis_block ;
|
|
||||||
protocol = genesis_protocol ;
|
|
||||||
}
|
|
||||||
|
|
||||||
module Sockaddr = struct
|
|
||||||
|
|
||||||
type t = V6.t * int
|
|
||||||
|
|
||||||
let of_string str =
|
|
||||||
match String.rindex str ':' with
|
|
||||||
| exception Not_found -> `Error "not a sockaddr"
|
|
||||||
| pos ->
|
|
||||||
let len = String.length str in
|
|
||||||
let addr, port =
|
|
||||||
String.sub str 0 pos, String.sub str (pos+1) (len - pos - 1) in
|
|
||||||
match Ipaddr.of_string_exn addr, int_of_string port with
|
|
||||||
| exception Failure _ -> `Error "not a sockaddr"
|
|
||||||
| V4 ipv4, port -> `Ok (Ipaddr.v6_of_v4 ipv4, port)
|
|
||||||
| V6 ipv6, port -> `Ok (ipv6, port)
|
|
||||||
|
|
||||||
let of_string_exn str =
|
|
||||||
match of_string str with
|
|
||||||
| `Ok saddr -> saddr
|
|
||||||
| `Error msg -> invalid_arg msg
|
|
||||||
|
|
||||||
let pp fmt (ip, port) = Format.fprintf fmt "%a:%d" V6.pp_hum ip port
|
|
||||||
let to_string saddr = Format.asprintf "%a" pp saddr
|
|
||||||
|
|
||||||
let encoding =
|
|
||||||
Data_encoding.conv to_string of_string_exn Data_encoding.string
|
|
||||||
|
|
||||||
let converter : t Cmdliner.Arg.converter = of_string, pp
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
module Cfg_file = struct
|
|
||||||
|
|
||||||
open Data_encoding
|
|
||||||
|
|
||||||
type t = {
|
|
||||||
db : db ;
|
|
||||||
net : net ;
|
|
||||||
rpc : rpc ;
|
|
||||||
log : log ;
|
|
||||||
}
|
|
||||||
|
|
||||||
and db = {
|
|
||||||
store : string ;
|
|
||||||
context : string ;
|
|
||||||
protocol : string ;
|
|
||||||
}
|
|
||||||
|
|
||||||
and net = {
|
|
||||||
identity : string ;
|
|
||||||
expected_pow : float ;
|
|
||||||
bootstrap_peers : Sockaddr.t list ;
|
|
||||||
peers_metadata : string ;
|
|
||||||
listen_addr : Sockaddr.t option ;
|
|
||||||
closed : bool ;
|
|
||||||
limits : P2p.limits ;
|
|
||||||
}
|
|
||||||
|
|
||||||
and rpc = {
|
|
||||||
listen_addr : Sockaddr.t option ;
|
|
||||||
cors_origins : string list ;
|
|
||||||
cors_headers : string list ;
|
|
||||||
tls : tls option ;
|
|
||||||
}
|
|
||||||
|
|
||||||
and tls = {
|
|
||||||
cert : string ;
|
|
||||||
key : string ;
|
|
||||||
}
|
|
||||||
|
|
||||||
and log = {
|
|
||||||
output : Logging.kind ;
|
|
||||||
default_level : Logging.level ;
|
|
||||||
rules : string option ;
|
|
||||||
template : Logging.template ;
|
|
||||||
}
|
|
||||||
|
|
||||||
let default_net_limits : P2p.limits = {
|
|
||||||
authentification_timeout = 5. ;
|
|
||||||
min_connections = 50 ;
|
|
||||||
expected_connections = 100 ;
|
|
||||||
max_connections = 200 ;
|
|
||||||
backlog = 20 ;
|
|
||||||
max_incoming_connections = 20 ;
|
|
||||||
max_download_speed = None ;
|
|
||||||
max_upload_speed = None ;
|
|
||||||
read_buffer_size = 1 lsl 14 ;
|
|
||||||
read_queue_size = None ;
|
|
||||||
write_queue_size = None ;
|
|
||||||
incoming_app_message_queue_size = None ;
|
|
||||||
incoming_message_queue_size = None ;
|
|
||||||
outgoing_message_queue_size = None ;
|
|
||||||
}
|
|
||||||
|
|
||||||
let default_net base_dir = {
|
|
||||||
identity = base_dir // "identity.json" ;
|
|
||||||
expected_pow = 24. ;
|
|
||||||
bootstrap_peers = [] ;
|
|
||||||
peers_metadata = base_dir // "peers.json" ;
|
|
||||||
listen_addr = Some (V6.unspecified, 9732) ;
|
|
||||||
closed = false ;
|
|
||||||
limits = default_net_limits ;
|
|
||||||
}
|
|
||||||
|
|
||||||
let default_rpc = {
|
|
||||||
listen_addr = None ;
|
|
||||||
cors_origins = [] ;
|
|
||||||
cors_headers = [] ;
|
|
||||||
tls = None ;
|
|
||||||
}
|
|
||||||
|
|
||||||
let default_log = {
|
|
||||||
output = Stderr ;
|
|
||||||
default_level = Notice ;
|
|
||||||
rules = None ;
|
|
||||||
template = Logging.default_template ;
|
|
||||||
}
|
|
||||||
|
|
||||||
let default_db base_dir = {
|
|
||||||
store = base_dir // "store" ;
|
|
||||||
context = base_dir // "context" ;
|
|
||||||
protocol = base_dir // "protocol" ;
|
|
||||||
}
|
|
||||||
|
|
||||||
let default_config base_dir = {
|
|
||||||
db = default_db base_dir ;
|
|
||||||
net = default_net base_dir ;
|
|
||||||
rpc = default_rpc ;
|
|
||||||
log = default_log ;
|
|
||||||
}
|
|
||||||
|
|
||||||
let db =
|
|
||||||
let default = default_db default_base_dir in
|
|
||||||
conv
|
|
||||||
(fun { store ; context ; protocol } ->
|
|
||||||
(store, context, protocol))
|
|
||||||
(fun (store, context, protocol) ->
|
|
||||||
{ store ; context ; protocol })
|
|
||||||
(obj3
|
|
||||||
(dft "store" string default.store)
|
|
||||||
(dft "context" string default.context)
|
|
||||||
(dft "protocol" string default.protocol))
|
|
||||||
|
|
||||||
let limit : P2p.limits Data_encoding.t =
|
|
||||||
conv
|
|
||||||
(fun { P2p.authentification_timeout ;
|
|
||||||
min_connections ; expected_connections ; max_connections ;
|
|
||||||
backlog ; max_incoming_connections ;
|
|
||||||
max_download_speed ; max_upload_speed ;
|
|
||||||
read_buffer_size ; read_queue_size ; write_queue_size ;
|
|
||||||
incoming_app_message_queue_size ;
|
|
||||||
incoming_message_queue_size ; outgoing_message_queue_size } ->
|
|
||||||
( ( authentification_timeout, min_connections, expected_connections,
|
|
||||||
max_connections, backlog, max_incoming_connections,
|
|
||||||
max_download_speed, max_upload_speed) ,
|
|
||||||
( read_buffer_size, read_queue_size, write_queue_size,
|
|
||||||
incoming_app_message_queue_size,
|
|
||||||
incoming_message_queue_size, outgoing_message_queue_size )))
|
|
||||||
(fun ( ( authentification_timeout, min_connections, expected_connections,
|
|
||||||
max_connections, backlog, max_incoming_connections,
|
|
||||||
max_download_speed, max_upload_speed) ,
|
|
||||||
( read_buffer_size, read_queue_size, write_queue_size,
|
|
||||||
incoming_app_message_queue_size,
|
|
||||||
incoming_message_queue_size, outgoing_message_queue_size ) ) ->
|
|
||||||
{ authentification_timeout ; min_connections ; expected_connections ;
|
|
||||||
max_connections ; backlog ; max_incoming_connections ;
|
|
||||||
max_download_speed ; max_upload_speed ;
|
|
||||||
read_buffer_size ; read_queue_size ; write_queue_size ;
|
|
||||||
incoming_app_message_queue_size ;
|
|
||||||
incoming_message_queue_size ; outgoing_message_queue_size })
|
|
||||||
(merge_objs
|
|
||||||
(obj8
|
|
||||||
(dft "authentification_timeout"
|
|
||||||
float default_net_limits.authentification_timeout)
|
|
||||||
(dft "min_connections" int31
|
|
||||||
default_net_limits.min_connections)
|
|
||||||
(dft "expected_connections" int31
|
|
||||||
default_net_limits.expected_connections)
|
|
||||||
(dft "max_connections" int31
|
|
||||||
default_net_limits.max_connections)
|
|
||||||
(dft "backlog" int31
|
|
||||||
default_net_limits.backlog)
|
|
||||||
(dft "max_incoming_connections" int31
|
|
||||||
default_net_limits.max_incoming_connections)
|
|
||||||
(opt "max_download_speed" int31)
|
|
||||||
(opt "max_upload_speed" int31))
|
|
||||||
(obj6
|
|
||||||
(dft "read_buffer_size" int31
|
|
||||||
default_net_limits.read_buffer_size)
|
|
||||||
(opt "read_queue_size" int31)
|
|
||||||
(opt "write_queue_size" int31)
|
|
||||||
(opt "incoming_app_message_queue_size" int31)
|
|
||||||
(opt "incoming_message_queue_size" int31)
|
|
||||||
(opt "outgoing_message_queue_size" int31)))
|
|
||||||
|
|
||||||
let net =
|
|
||||||
let default = default_net default_base_dir in
|
|
||||||
conv
|
|
||||||
(fun { identity ; expected_pow ; bootstrap_peers ; peers_metadata ;
|
|
||||||
listen_addr ; closed ; limits } ->
|
|
||||||
( identity, expected_pow, bootstrap_peers, peers_metadata,
|
|
||||||
listen_addr, closed, limits ))
|
|
||||||
(fun ( identity, expected_pow, bootstrap_peers, peers_metadata,
|
|
||||||
listen_addr, closed, limits ) ->
|
|
||||||
{ identity ; expected_pow ; bootstrap_peers ; peers_metadata ;
|
|
||||||
listen_addr ; closed ; limits })
|
|
||||||
(obj7
|
|
||||||
(dft "identity" string default.identity)
|
|
||||||
(dft "expected-proof-or-work" float default.expected_pow)
|
|
||||||
(dft "bootstrap_peers"
|
|
||||||
(list Sockaddr.encoding) default.bootstrap_peers)
|
|
||||||
(dft "peers-metadata" string default.peers_metadata)
|
|
||||||
(opt "listen-addr" Sockaddr.encoding)
|
|
||||||
(dft "closed" bool false)
|
|
||||||
(dft "limits" limit default_net_limits))
|
|
||||||
|
|
||||||
let rpc : rpc Data_encoding.t =
|
|
||||||
conv
|
|
||||||
(fun { cors_origins ; cors_headers ; listen_addr ; tls } ->
|
|
||||||
let cert, key =
|
|
||||||
match tls with
|
|
||||||
| None -> None, None
|
|
||||||
| Some { cert ; key } -> Some cert, Some key in
|
|
||||||
(listen_addr, cors_origins, cors_headers, cert, key ))
|
|
||||||
(fun (listen_addr, cors_origins, cors_headers, cert, key ) ->
|
|
||||||
let tls =
|
|
||||||
match cert, key with
|
|
||||||
| None, _ | _, None -> None
|
|
||||||
| Some cert, Some key -> Some { cert ; key } in
|
|
||||||
{ listen_addr ; cors_origins ; cors_headers ; tls })
|
|
||||||
(obj5
|
|
||||||
(opt "listen-addr" Sockaddr.encoding)
|
|
||||||
(dft "cors-origin" (list string) default_rpc.cors_origins)
|
|
||||||
(dft "cors-headers" (list string) default_rpc.cors_headers)
|
|
||||||
(opt "crt" string)
|
|
||||||
(opt "key" string))
|
|
||||||
|
|
||||||
let log =
|
|
||||||
conv
|
|
||||||
(fun {output ; default_level ; rules ; template } ->
|
|
||||||
(output, default_level, rules, template))
|
|
||||||
(fun (output, default_level, rules, template) ->
|
|
||||||
{ output ; default_level ; rules ; template })
|
|
||||||
(obj4
|
|
||||||
(dft "output" Logging.kind_encoding default_log.output)
|
|
||||||
(dft "level" Logging.level_encoding default_log.default_level)
|
|
||||||
(opt "rules" string)
|
|
||||||
(dft "template" string default_log.template))
|
|
||||||
|
|
||||||
let encoding =
|
|
||||||
conv
|
|
||||||
(fun { db ; rpc ; net ; log } -> (db, rpc, net, log))
|
|
||||||
(fun (db, rpc, net, log) -> { db ; rpc ; net ; log })
|
|
||||||
(obj4
|
|
||||||
(dft "db" db (default_db default_base_dir))
|
|
||||||
(dft "rpc" rpc default_rpc)
|
|
||||||
(req "net" net)
|
|
||||||
(dft "log" log default_log))
|
|
||||||
|
|
||||||
let read fp =
|
|
||||||
Data_encoding_ezjsonm.read_file fp >>=? fun json ->
|
|
||||||
try return (Data_encoding.Json.destruct encoding json)
|
|
||||||
with exn -> fail (Exn exn)
|
|
||||||
|
|
||||||
let write fp cfg =
|
|
||||||
Data_encoding_ezjsonm.write_file fp
|
|
||||||
(Data_encoding.Json.construct encoding cfg)
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
module Cmdline = struct
|
|
||||||
|
|
||||||
type t = {
|
|
||||||
sandbox : string option option ;
|
|
||||||
verbosity : Logging.level option ;
|
|
||||||
generate_identity : bool ;
|
|
||||||
write_cfg : 'a 'b 'c 'd. (string * (string -> 'a, 'b, 'c, 'a) format4) option ;
|
|
||||||
}
|
|
||||||
|
|
||||||
open Cmdliner
|
|
||||||
|
|
||||||
(* cli args *)
|
|
||||||
let misc_sect = "MISC"
|
|
||||||
|
|
||||||
let base_dir =
|
|
||||||
let doc =
|
|
||||||
"The directory where the Tezos node will store all its data." in
|
|
||||||
Arg.(value & opt (some string) None &
|
|
||||||
info ~docs:"CONFIG" ~doc ~docv:"DIR" ["base-dir"])
|
|
||||||
|
|
||||||
let config_file =
|
|
||||||
let doc = "The main configuration file." in
|
|
||||||
Arg.(value & opt (some string) None &
|
|
||||||
info ~docs:"CONFIG" ~doc ~docv:"FILE" ["config-file"])
|
|
||||||
|
|
||||||
let sandbox =
|
|
||||||
let doc =
|
|
||||||
"Run the daemon in a sandbox: P2P is disabled, and constants of \
|
|
||||||
the economical protocol might be altered by the optionnal JSON file."
|
|
||||||
in
|
|
||||||
Arg.(value & opt ~vopt:(Some None) (some (some string)) None &
|
|
||||||
info ~docs:"NETWORK" ~doc ~docv:"FILE.json" ["sandbox"])
|
|
||||||
|
|
||||||
let verbosity =
|
|
||||||
let doc =
|
|
||||||
"Increase log level. \
|
|
||||||
Use several times to increase log level, e.g. `-vv'." in
|
|
||||||
Arg.(value & flag_all & info ~docs:misc_sect ~doc ["v"])
|
|
||||||
|
|
||||||
let reset_config =
|
|
||||||
let doc = "Overwrite config file with factory defaults." in
|
|
||||||
Arg.(value & flag & info ~docs:"CONFIG" ~doc ["reset-config"])
|
|
||||||
|
|
||||||
let update_config =
|
|
||||||
let doc = "Update config file with values from the command line." in
|
|
||||||
Arg.(value & flag & info ~docs:"CONFIG" ~doc ["update-config"])
|
|
||||||
|
|
||||||
let generate_identity =
|
|
||||||
let doc =
|
|
||||||
"Generate a new cryptographic identity for the node. \
|
|
||||||
It also generates the associated stamp of proof-of-work. \
|
|
||||||
See `--expected-pow` for adjusting the required amount of \
|
|
||||||
proof-of-work" in
|
|
||||||
Arg.(value & flag & info ~docs:"CONFIG" ~doc ["generate-identity"])
|
|
||||||
|
|
||||||
(* net args *)
|
|
||||||
let expected_connections =
|
|
||||||
let doc =
|
|
||||||
"The number of running connections that we must try to maintain
|
|
||||||
(approximativaly)." in
|
|
||||||
Arg.(value & opt (some int) None &
|
|
||||||
info ~docs:"NETWORK" ~doc ~docv:"NUM" ["expected-connections"])
|
|
||||||
|
|
||||||
let max_download_speed =
|
|
||||||
let doc =
|
|
||||||
"The maximum number of bytes read per second." in
|
|
||||||
Arg.(value & opt (some int) None &
|
|
||||||
info ~docs:"NETWORK" ~doc ~docv:"NUM" ["max-download-speed"])
|
|
||||||
|
|
||||||
let max_upload_speed =
|
|
||||||
let doc =
|
|
||||||
"The maximum number of bytes sent per second." in
|
|
||||||
Arg.(value & opt (some int) None &
|
|
||||||
info ~docs:"NETWORK" ~doc ~docv:"NUM" ["max-upload-speed"])
|
|
||||||
|
|
||||||
let listen_addr =
|
|
||||||
let doc =
|
|
||||||
"The TCP address and port at which this instance can be reached." in
|
|
||||||
Arg.(value & opt (some Sockaddr.converter) None &
|
|
||||||
info ~docs:"NETWORK" ~doc ~docv:"ADDR:PORT" ["net-addr"])
|
|
||||||
|
|
||||||
let peers =
|
|
||||||
let doc =
|
|
||||||
"A peer to bootstrap the network from. \
|
|
||||||
Can be used several times to add several peers." in
|
|
||||||
Arg.(value & opt_all Sockaddr.converter [] &
|
|
||||||
info ~docs:"NETWORK" ~doc ~docv:"ADDR:PORT" ["peer"])
|
|
||||||
|
|
||||||
let expected_pow =
|
|
||||||
let doc =
|
|
||||||
"Expected level of proof-of-work for peers identity." in
|
|
||||||
Arg.(value & opt (some float) None &
|
|
||||||
info ~docs:"NETWORK" ~doc ~docv:"FLOAT" ["expected-pow"])
|
|
||||||
|
|
||||||
let closed =
|
|
||||||
let doc =
|
|
||||||
"Only accept connections from the configured bootstrap peers." in
|
|
||||||
Arg.(value & flag & info ~docs:"NETWORK" ~doc ["closed"])
|
|
||||||
|
|
||||||
(* rpc args *)
|
|
||||||
let rpc_listen_addr =
|
|
||||||
let doc =
|
|
||||||
"The TCP socket address at which this RPC server \
|
|
||||||
instance can be reached." in
|
|
||||||
Arg.(value & opt (some Sockaddr.converter) None &
|
|
||||||
info ~docs:"RPC" ~doc ~docv:"ADDR:PORT" ["rpc-addr"])
|
|
||||||
|
|
||||||
let rpc_tls =
|
|
||||||
let doc =
|
|
||||||
"Enable TLS for this RPC server \
|
|
||||||
with the provided certificate and key." in
|
|
||||||
Arg.(value & opt (some (pair string string)) None &
|
|
||||||
info ~docs:"RPC" ~doc ~docv:"crt,key" ["rpc-tls"])
|
|
||||||
|
|
||||||
let cors_origins =
|
|
||||||
let doc =
|
|
||||||
"CORS origin allowed by the RPC server \
|
|
||||||
via Access-Control-Allow-Origin; may be used multiple times" in
|
|
||||||
Arg.(value & opt_all string [] &
|
|
||||||
info ~docs:"RPC" ~doc ~docv:"ORIGIN" ["cors-origin"])
|
|
||||||
|
|
||||||
let cors_headers =
|
|
||||||
let doc =
|
|
||||||
"Header reported by Access-Control-Allow-Headers \
|
|
||||||
reported during CORS preflighting; may be used multiple times" in
|
|
||||||
Arg.(value & opt_all string [] &
|
|
||||||
info ~docs:"RPC" ~doc ~docv:"HEADER" ["cors-header"])
|
|
||||||
|
|
||||||
exception Fail of string
|
|
||||||
let fail fmt =
|
|
||||||
Format.kasprintf (fun msg -> Lwt.fail (Fail msg)) fmt
|
|
||||||
|
|
||||||
let parse
|
|
||||||
base_dir config_file
|
|
||||||
sandbox verbosity
|
|
||||||
expected_connections
|
|
||||||
max_download_speed max_upload_speed
|
|
||||||
listen_addr bootstrap_peers closed expected_pow
|
|
||||||
rpc_listen_addr rpc_tls cors_origins cors_headers
|
|
||||||
reset_cfg update_cfg generate_identity =
|
|
||||||
|
|
||||||
let actual_base_dir =
|
|
||||||
match base_dir with
|
|
||||||
| None -> default_base_dir
|
|
||||||
| Some dir -> dir in
|
|
||||||
|
|
||||||
let config_file =
|
|
||||||
match config_file with
|
|
||||||
| None -> actual_base_dir // "config.json"
|
|
||||||
| Some file -> file in
|
|
||||||
|
|
||||||
(* When --base-dir is provided, we ignore the `db`, `net.identity`
|
|
||||||
and `net.peers_metadata` of the configuration file. *)
|
|
||||||
let db = Utils.map_option Cfg_file.default_db base_dir in
|
|
||||||
let identity, peers_metadata =
|
|
||||||
let default_net = Utils.map_option Cfg_file.default_net base_dir in
|
|
||||||
Utils.map_option
|
|
||||||
~f:(fun net -> net.Cfg_file.identity) default_net,
|
|
||||||
Utils.map_option
|
|
||||||
~f:(fun net -> net.Cfg_file.peers_metadata) default_net in
|
|
||||||
|
|
||||||
let read () =
|
|
||||||
if reset_cfg && update_cfg then
|
|
||||||
fail "The options --reset-config and --update-config \
|
|
||||||
cannot be used together"
|
|
||||||
else if reset_cfg then
|
|
||||||
Lwt.return
|
|
||||||
(Cfg_file.default_config actual_base_dir, true)
|
|
||||||
else if update_cfg && not (Sys.file_exists config_file) then
|
|
||||||
fail "Cannot update a non-existant configuration file."
|
|
||||||
else if not (Sys.file_exists config_file) then
|
|
||||||
Lwt.return
|
|
||||||
(Cfg_file.default_config actual_base_dir, true)
|
|
||||||
else
|
|
||||||
Cfg_file.read config_file >>= function
|
|
||||||
| Error err ->
|
|
||||||
fail
|
|
||||||
"@[Corrupted configuration file, \
|
|
||||||
fix it or use --reset-config.@ %a@]"
|
|
||||||
pp_print_error err
|
|
||||||
| Ok cfg -> Lwt.return (cfg, update_cfg)
|
|
||||||
in
|
|
||||||
|
|
||||||
let verbosity =
|
|
||||||
match verbosity with
|
|
||||||
| [] -> None
|
|
||||||
| [_] -> Some Logging.Info
|
|
||||||
| _ -> Some Logging.Debug
|
|
||||||
in
|
|
||||||
|
|
||||||
let rpc_tls =
|
|
||||||
Utils.map_option
|
|
||||||
(fun (cert, key) -> { Cfg_file.cert ; key })
|
|
||||||
rpc_tls in
|
|
||||||
|
|
||||||
let unopt_list ~default = function
|
|
||||||
| [] -> default
|
|
||||||
| l -> l in
|
|
||||||
|
|
||||||
(* when `--expected-connections` is used,
|
|
||||||
override all the bounds defined in the configuration file. *)
|
|
||||||
let min_connections, expected_connections, max_connections =
|
|
||||||
match expected_connections with
|
|
||||||
| None -> None, None, None
|
|
||||||
| Some x -> Some (x/2), Some x, Some (3*x/2) in
|
|
||||||
|
|
||||||
try
|
|
||||||
Lwt_main.run begin
|
|
||||||
Lwt_utils.create_dir ~perm:0o700 actual_base_dir >>= fun () ->
|
|
||||||
read () >>= fun (cfg, write_cfg) ->
|
|
||||||
let db = Utils.unopt ~default:cfg.db db in
|
|
||||||
let limits : P2p.limits = {
|
|
||||||
cfg.net.limits with
|
|
||||||
min_connections =
|
|
||||||
Utils.unopt
|
|
||||||
~default:cfg.net.limits.min_connections
|
|
||||||
min_connections ;
|
|
||||||
expected_connections =
|
|
||||||
Utils.unopt
|
|
||||||
~default:cfg.net.limits.expected_connections
|
|
||||||
expected_connections ;
|
|
||||||
max_connections =
|
|
||||||
Utils.unopt
|
|
||||||
~default:cfg.net.limits.max_connections
|
|
||||||
max_connections ;
|
|
||||||
max_download_speed =
|
|
||||||
Utils.first_some
|
|
||||||
max_download_speed cfg.net.limits.max_download_speed ;
|
|
||||||
max_upload_speed =
|
|
||||||
Utils.first_some
|
|
||||||
max_upload_speed cfg.net.limits.max_upload_speed ;
|
|
||||||
} in
|
|
||||||
let net : Cfg_file.net = {
|
|
||||||
identity =
|
|
||||||
Utils.unopt ~default:cfg.net.identity identity ;
|
|
||||||
expected_pow =
|
|
||||||
Utils.unopt ~default:cfg.net.expected_pow expected_pow ;
|
|
||||||
bootstrap_peers =
|
|
||||||
unopt_list ~default:cfg.net.bootstrap_peers bootstrap_peers ;
|
|
||||||
peers_metadata =
|
|
||||||
Utils.unopt ~default:cfg.net.peers_metadata peers_metadata ;
|
|
||||||
listen_addr =
|
|
||||||
Utils.first_some listen_addr cfg.net.listen_addr ;
|
|
||||||
closed = cfg.net.closed || closed ;
|
|
||||||
limits ;
|
|
||||||
}
|
|
||||||
and rpc : Cfg_file.rpc = {
|
|
||||||
listen_addr =
|
|
||||||
Utils.first_some rpc_listen_addr cfg.rpc.listen_addr ;
|
|
||||||
cors_origins =
|
|
||||||
unopt_list ~default:cfg.rpc.cors_origins cors_origins ;
|
|
||||||
cors_headers =
|
|
||||||
unopt_list ~default:cfg.rpc.cors_headers cors_headers ;
|
|
||||||
tls =
|
|
||||||
Utils.first_some rpc_tls cfg.rpc.tls ;
|
|
||||||
} in
|
|
||||||
let cfg_file = { Cfg_file.db ; net ; rpc ; log = cfg.log } in
|
|
||||||
let write_cfg : (string * _ format6) option =
|
|
||||||
if not write_cfg then None
|
|
||||||
else if reset_cfg then
|
|
||||||
Some (config_file, "Reseting configuration file '%s'.")
|
|
||||||
else if update_cfg then
|
|
||||||
Some (config_file, "Updating configuration file '%s'.")
|
|
||||||
else
|
|
||||||
Some (config_file, "Writing initial configuration file '%s'.")
|
|
||||||
in
|
|
||||||
let cmdline =
|
|
||||||
{ sandbox ; verbosity ; generate_identity ; write_cfg } in
|
|
||||||
Lwt.return (`Ok (cfg_file, cmdline))
|
|
||||||
end
|
|
||||||
with Fail msg -> `Error (false, msg)
|
|
||||||
|
|
||||||
let cmd =
|
|
||||||
let open Term in
|
|
||||||
ret (const parse $ base_dir $ config_file
|
|
||||||
$ sandbox $ verbosity
|
|
||||||
$ expected_connections
|
|
||||||
$ max_download_speed $ max_upload_speed
|
|
||||||
$ listen_addr $ peers $ closed $ expected_pow
|
|
||||||
$ rpc_listen_addr $ rpc_tls $ cors_origins $ cors_headers
|
|
||||||
$ reset_config $ update_config $ generate_identity
|
|
||||||
),
|
|
||||||
let doc = "The Tezos daemon" in
|
|
||||||
let man = [
|
|
||||||
`S "NETWORK";
|
|
||||||
`S "RPC";
|
|
||||||
`S "CONFIG";
|
|
||||||
`S misc_sect;
|
|
||||||
`S "EXAMPLES" ;
|
|
||||||
`P "Use `$(mname) --sandbox \
|
|
||||||
--base-dir /path/to/a/custom/data/dir \
|
|
||||||
--rpc-addr :::8732' \
|
|
||||||
to run a single instance in sandbox mode, \
|
|
||||||
listening to RPC commands at localhost port 8732.";
|
|
||||||
`P "Use `$(mname)' for a node that accepts network connections.";
|
|
||||||
`S "BUGS"; `P "Check bug reports at https://github.com/tezos/tezos/issues.";
|
|
||||||
]
|
|
||||||
in
|
|
||||||
info ~sdocs:misc_sect ~man ~doc "tezos-node"
|
|
||||||
|
|
||||||
let parse () = Term.eval cmd
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
let init_logger ?verbosity (log_config : Cfg_file.log) =
|
|
||||||
let open Logging in
|
|
||||||
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 Lwt_log_core.load_rules rules
|
|
||||||
end ;
|
|
||||||
Logging.init ~template:log_config.template log_config.output
|
|
||||||
|
|
||||||
type error += No_identity
|
|
||||||
type error += Existent_identity_file
|
|
||||||
|
|
||||||
let read_identity target file =
|
|
||||||
Lwt_unix.file_exists file >>= function
|
|
||||||
| true ->
|
|
||||||
Data_encoding_ezjsonm.read_file file >>=? fun json ->
|
|
||||||
let id = Data_encoding.Json.destruct P2p.Identity.encoding json in
|
|
||||||
Lwt_utils.unless
|
|
||||||
(Crypto_box.check_proof_of_work
|
|
||||||
id.public_key id.proof_of_work_stamp target)
|
|
||||||
(fun () ->
|
|
||||||
lwt_warn "The amount of proof-of-work stamp in the node's identity \
|
|
||||||
is below your own expectations.") >>= fun () ->
|
|
||||||
return id
|
|
||||||
| false ->
|
|
||||||
fail No_identity
|
|
||||||
|
|
||||||
let init_node ?sandbox (config : Cfg_file.t) =
|
|
||||||
let patch_context json ctxt =
|
|
||||||
let module Proto = (val Updater.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 ->
|
|
||||||
begin
|
|
||||||
let open P2p in
|
|
||||||
match sandbox with
|
|
||||||
| Some _ -> return None
|
|
||||||
| None ->
|
|
||||||
let proof_of_work_target =
|
|
||||||
Crypto_box.make_target config.net.expected_pow in
|
|
||||||
read_identity
|
|
||||||
proof_of_work_target config.net.identity >>=? fun identity ->
|
|
||||||
lwt_log_notice "Peers' id: %a" P2p.Gid.pp identity.gid >>= fun () ->
|
|
||||||
let p2p_config : P2p.config =
|
|
||||||
{ listening_port = Utils.map_option snd config.net.listen_addr ;
|
|
||||||
listening_addr = Utils.map_option fst config.net.listen_addr ;
|
|
||||||
trusted_points = config.net.bootstrap_peers ;
|
|
||||||
peers_file = config.net.peers_metadata ;
|
|
||||||
closed_network = config.net.closed ;
|
|
||||||
identity ;
|
|
||||||
proof_of_work_target ;
|
|
||||||
}
|
|
||||||
in
|
|
||||||
return (Some (p2p_config, config.net.limits))
|
|
||||||
end >>=? fun p2p_config ->
|
|
||||||
let node_config : Node.config = {
|
|
||||||
genesis ;
|
|
||||||
test_protocol ;
|
|
||||||
patch_context ;
|
|
||||||
store_root = config.db.store ;
|
|
||||||
context_root = config.db.context ;
|
|
||||||
p2p = p2p_config ;
|
|
||||||
} in
|
|
||||||
Node.create node_config
|
|
||||||
|
|
||||||
let () =
|
let () =
|
||||||
let old_hook = !Lwt.async_exception_hook in
|
match Cmdliner.Term.eval_choice (term, info) commands with
|
||||||
Lwt.async_exception_hook := function
|
|
||||||
| Ssl.Read_error _ -> ()
|
|
||||||
| exn -> old_hook exn
|
|
||||||
|
|
||||||
let init_rpc (rpc_config: Cfg_file.rpc) node =
|
|
||||||
match rpc_config.listen_addr with
|
|
||||||
| None ->
|
|
||||||
lwt_log_notice "Not listening to RPC calls." >>= fun () ->
|
|
||||||
Lwt.return_none
|
|
||||||
| Some (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 ->
|
|
||||||
Lwt.return (Some server)
|
|
||||||
|
|
||||||
|
|
||||||
let init_signal () =
|
|
||||||
let handler id = try Lwt_exit.exit id with _ -> () in
|
|
||||||
ignore (Lwt_unix.on_signal Sys.sigint handler : Lwt_unix.signal_handler_id)
|
|
||||||
|
|
||||||
module Identity = struct
|
|
||||||
|
|
||||||
let generate (command : Cmdline.t) (config : Cfg_file.t) =
|
|
||||||
let file = config.net.identity in
|
|
||||||
if not command.generate_identity then
|
|
||||||
return ()
|
|
||||||
else if Sys.file_exists file then
|
|
||||||
fail Existent_identity_file
|
|
||||||
else
|
|
||||||
let target = Crypto_box.make_target config.net.expected_pow in
|
|
||||||
Format.eprintf "Generating a new identity... " ;
|
|
||||||
let identity =
|
|
||||||
P2p.Identity.generate_with_animation Format.err_formatter target in
|
|
||||||
Data_encoding_ezjsonm.write_file file
|
|
||||||
(Data_encoding.Json.construct P2p.Identity.encoding identity)
|
|
||||||
>>=? fun () ->
|
|
||||||
Format.eprintf
|
|
||||||
"Stored the new identity (%a) into '%s'@."
|
|
||||||
P2p.Gid.pp identity.gid file ;
|
|
||||||
return ()
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
module Node = struct
|
|
||||||
|
|
||||||
let may_write_config (command : Cmdline.t) (config : Cfg_file.t) =
|
|
||||||
match command.write_cfg with
|
|
||||||
| None -> return ()
|
|
||||||
| Some (file, fmt) ->
|
|
||||||
Format.eprintf "%(%s%)@." fmt file ;
|
|
||||||
Cfg_file.write file config
|
|
||||||
|
|
||||||
let run (command : Cmdline.t) (config : Cfg_file.t) =
|
|
||||||
may_write_config command config >>=? fun () ->
|
|
||||||
init_signal () ;
|
|
||||||
init_logger ?verbosity:command.verbosity config.log >>= fun () ->
|
|
||||||
Updater.init config.db.protocol ;
|
|
||||||
lwt_log_notice "Starting the Tezos node..." >>= fun () ->
|
|
||||||
init_node ?sandbox:command.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 RPC_server.shutdown rpc >>= fun () ->
|
|
||||||
lwt_log_notice "BYE (%d)" x >>= fun () ->
|
|
||||||
return ()
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
let main (command : Cmdline.t) (config : Cfg_file.t) =
|
|
||||||
Random.self_init () ;
|
|
||||||
Sodium.Random.stir () ;
|
|
||||||
Identity.generate command config >>=? fun () ->
|
|
||||||
Node.run command config
|
|
||||||
|
|
||||||
let () =
|
|
||||||
match Cmdline.parse () with
|
|
||||||
| `Error _ -> exit 1
|
| `Error _ -> exit 1
|
||||||
| `Help -> exit 1
|
| `Help -> exit 0
|
||||||
| `Version -> exit 1
|
| `Version -> exit 1
|
||||||
| `Ok (config, command) ->
|
| `Ok () -> exit 0
|
||||||
Lwt_main.run begin
|
|
||||||
main command config >>= function
|
|
||||||
| Ok () -> Lwt.return_unit
|
|
||||||
| Error [No_identity] ->
|
|
||||||
Format.eprintf
|
|
||||||
"Cannot find the identity file '%s'!\n%a@."
|
|
||||||
config.net.identity
|
|
||||||
Utils.display_paragraph
|
|
||||||
(Format.sprintf
|
|
||||||
"In order to proceed, Tezos needs a cryptographic identity. \
|
|
||||||
You may generate a new identity by running:\n\
|
|
||||||
\n\
|
|
||||||
%s --generate-identity --expected-pow %.1f\n\
|
|
||||||
where `%.1f` is the expected level of proof-of-work in \
|
|
||||||
the stamp associated to the new identity. \
|
|
||||||
For quick testing, you may use '--expected-pow 0'."
|
|
||||||
Sys.argv.(0)
|
|
||||||
config.net.expected_pow
|
|
||||||
config.net.expected_pow) ;
|
|
||||||
exit 2
|
|
||||||
| Error [Existent_identity_file] ->
|
|
||||||
Format.eprintf
|
|
||||||
"Error: Cannot implicitely overwrite an existing identity.\n\
|
|
||||||
\n\
|
|
||||||
\ Please remove the old identity file '%s'.@."
|
|
||||||
config.net.identity ;
|
|
||||||
exit 2
|
|
||||||
| Error err ->
|
|
||||||
lwt_log_error
|
|
||||||
"@[<v 2>Unexpected error while initializing the node:@ %a@]@."
|
|
||||||
pp_print_error err >>= fun () ->
|
|
||||||
exit 1
|
|
||||||
end
|
|
||||||
|
@ -295,9 +295,15 @@ module Make() = struct
|
|||||||
if cond then return () else f ()
|
if cond then return () else f ()
|
||||||
|
|
||||||
let pp_print_error ppf errors =
|
let pp_print_error ppf errors =
|
||||||
Format.fprintf ppf "@[<v 2>Error, dumping error stack:@,%a@]@."
|
match errors with
|
||||||
(Format.pp_print_list pp)
|
| [] ->
|
||||||
(List.rev errors)
|
Format.fprintf ppf "Unknown error@."
|
||||||
|
| [error] ->
|
||||||
|
Format.fprintf ppf "@[<v 2>Error:@ %a@]@." pp error
|
||||||
|
| errors ->
|
||||||
|
Format.fprintf ppf "@[<v 2>Error, dumping error stack:@,%a@]@."
|
||||||
|
(Format.pp_print_list pp)
|
||||||
|
(List.rev errors)
|
||||||
|
|
||||||
type error += Unclassified of string
|
type error += Unclassified of string
|
||||||
|
|
||||||
|
@ -44,8 +44,11 @@ let ign_log_f
|
|||||||
(fun msg -> Lwt_log.ign_log ?exn ~section ?location ?logger ~level msg)
|
(fun msg -> Lwt_log.ign_log ?exn ~section ?location ?logger ~level msg)
|
||||||
format
|
format
|
||||||
|
|
||||||
|
let sections = ref []
|
||||||
|
|
||||||
module Make(S : sig val name: string end) : LOG = struct
|
module Make(S : sig val name: string end) : LOG = struct
|
||||||
|
|
||||||
|
let () = sections := S.name :: !sections
|
||||||
let section = Lwt_log.Section.make S.name
|
let section = Lwt_log.Section.make S.name
|
||||||
|
|
||||||
let debug fmt = ign_log_f ~section ~level:Lwt_log.Debug fmt
|
let debug fmt = ign_log_f ~section ~level:Lwt_log.Debug fmt
|
||||||
@ -91,81 +94,96 @@ module Webclient = Make(struct let name = "webclient" end)
|
|||||||
type template = Lwt_log.template
|
type template = Lwt_log.template
|
||||||
let default_template = "$(date) - $(section): $(message)"
|
let default_template = "$(date) - $(section): $(message)"
|
||||||
|
|
||||||
type kind =
|
module Output = struct
|
||||||
| Null
|
type t =
|
||||||
| Stdout
|
| Null
|
||||||
| Stderr
|
| Stdout
|
||||||
| File of string
|
| Stderr
|
||||||
| Syslog of Lwt_log.syslog_facility
|
| File of string
|
||||||
|
| Syslog of Lwt_log.syslog_facility
|
||||||
|
|
||||||
let kind_encoding =
|
let encoding =
|
||||||
let open Data_encoding in
|
let open Data_encoding in
|
||||||
conv
|
conv
|
||||||
(function
|
(function
|
||||||
| Null -> "/dev/null"
|
| Null -> "/dev/null"
|
||||||
| Stdout -> "stdout"
|
| Stdout -> "stdout"
|
||||||
| Stderr -> "stderr"
|
| Stderr -> "stderr"
|
||||||
| File fp -> fp
|
| File fp -> fp
|
||||||
| Syslog `Auth -> "syslog:auth"
|
| Syslog `Auth -> "syslog:auth"
|
||||||
| Syslog `Authpriv -> "syslog:authpriv"
|
| Syslog `Authpriv -> "syslog:authpriv"
|
||||||
| Syslog `Cron -> "syslog:cron"
|
| Syslog `Cron -> "syslog:cron"
|
||||||
| Syslog `Daemon -> "syslog:daemon"
|
| Syslog `Daemon -> "syslog:daemon"
|
||||||
| Syslog `FTP -> "syslog:ftp"
|
| Syslog `FTP -> "syslog:ftp"
|
||||||
| Syslog `Kernel -> "syslog:kernel"
|
| Syslog `Kernel -> "syslog:kernel"
|
||||||
| Syslog `Local0 -> "syslog:local0"
|
| Syslog `Local0 -> "syslog:local0"
|
||||||
| Syslog `Local1 -> "syslog:local1"
|
| Syslog `Local1 -> "syslog:local1"
|
||||||
| Syslog `Local2 -> "syslog:local2"
|
| Syslog `Local2 -> "syslog:local2"
|
||||||
| Syslog `Local3 -> "syslog:local3"
|
| Syslog `Local3 -> "syslog:local3"
|
||||||
| Syslog `Local4 -> "syslog:local4"
|
| Syslog `Local4 -> "syslog:local4"
|
||||||
| Syslog `Local5 -> "syslog:local5"
|
| Syslog `Local5 -> "syslog:local5"
|
||||||
| Syslog `Local6 -> "syslog:local6"
|
| Syslog `Local6 -> "syslog:local6"
|
||||||
| Syslog `Local7 -> "syslog:local7"
|
| Syslog `Local7 -> "syslog:local7"
|
||||||
| Syslog `LPR -> "syslog:lpr"
|
| Syslog `LPR -> "syslog:lpr"
|
||||||
| Syslog `Mail -> "syslog:mail"
|
| Syslog `Mail -> "syslog:mail"
|
||||||
| Syslog `News -> "syslog:news"
|
| Syslog `News -> "syslog:news"
|
||||||
| Syslog `Syslog -> "syslog:syslog"
|
| Syslog `Syslog -> "syslog:syslog"
|
||||||
| Syslog `User -> "syslog:user"
|
| Syslog `User -> "syslog:user"
|
||||||
| Syslog `UUCP -> "syslog:uucp"
|
| Syslog `UUCP -> "syslog:uucp"
|
||||||
| Syslog `NTP -> "syslog:ntp"
|
| Syslog `NTP -> "syslog:ntp"
|
||||||
| Syslog `Security -> "syslog:security"
|
| Syslog `Security -> "syslog:security"
|
||||||
| Syslog `Console -> "syslog:console")
|
| Syslog `Console -> "syslog:console")
|
||||||
(function
|
(function
|
||||||
| "/dev/null" | "null" -> Null
|
| "/dev/null" | "null" -> Null
|
||||||
| "stdout" -> Stdout
|
| "stdout" -> Stdout
|
||||||
| "stderr" -> Stderr
|
| "stderr" -> Stderr
|
||||||
| "syslog:auth" -> Syslog `Auth
|
| "syslog:auth" -> Syslog `Auth
|
||||||
| "syslog:authpriv" -> Syslog `Authpriv
|
| "syslog:authpriv" -> Syslog `Authpriv
|
||||||
| "syslog:cron" -> Syslog `Cron
|
| "syslog:cron" -> Syslog `Cron
|
||||||
| "syslog:daemon" -> Syslog `Daemon
|
| "syslog:daemon" -> Syslog `Daemon
|
||||||
| "syslog:ftp" -> Syslog `FTP
|
| "syslog:ftp" -> Syslog `FTP
|
||||||
| "syslog:kernel" -> Syslog `Kernel
|
| "syslog:kernel" -> Syslog `Kernel
|
||||||
| "syslog:local0" -> Syslog `Local0
|
| "syslog:local0" -> Syslog `Local0
|
||||||
| "syslog:local1" -> Syslog `Local1
|
| "syslog:local1" -> Syslog `Local1
|
||||||
| "syslog:local2" -> Syslog `Local2
|
| "syslog:local2" -> Syslog `Local2
|
||||||
| "syslog:local3" -> Syslog `Local3
|
| "syslog:local3" -> Syslog `Local3
|
||||||
| "syslog:local4" -> Syslog `Local4
|
| "syslog:local4" -> Syslog `Local4
|
||||||
| "syslog:local5" -> Syslog `Local5
|
| "syslog:local5" -> Syslog `Local5
|
||||||
| "syslog:local6" -> Syslog `Local6
|
| "syslog:local6" -> Syslog `Local6
|
||||||
| "syslog:local7" -> Syslog `Local7
|
| "syslog:local7" -> Syslog `Local7
|
||||||
| "syslog:lpr" -> Syslog `LPR
|
| "syslog:lpr" -> Syslog `LPR
|
||||||
| "syslog:mail" -> Syslog `Mail
|
| "syslog:mail" -> Syslog `Mail
|
||||||
| "syslog:news" -> Syslog `News
|
| "syslog:news" -> Syslog `News
|
||||||
| "syslog:syslog" -> Syslog `Syslog
|
| "syslog:syslog" -> Syslog `Syslog
|
||||||
| "syslog:user" -> Syslog `User
|
| "syslog:user" -> Syslog `User
|
||||||
| "syslog:uucp" -> Syslog `UUCP
|
| "syslog:uucp" -> Syslog `UUCP
|
||||||
| "syslog:ntp" -> Syslog `NTP
|
| "syslog:ntp" -> Syslog `NTP
|
||||||
| "syslog:security" -> Syslog `Security
|
| "syslog:security" -> Syslog `Security
|
||||||
| "syslog:console" -> Syslog `Console
|
| "syslog:console" -> Syslog `Console
|
||||||
(* | s when start_with "syslog:" FIXME error or warning. *)
|
(* | s when start_with "syslog:" FIXME error or warning. *)
|
||||||
| fp ->
|
| fp ->
|
||||||
(* TODO check absolute path *)
|
(* TODO check absolute path *)
|
||||||
File fp)
|
File fp)
|
||||||
string
|
string
|
||||||
|
|
||||||
|
let of_string str =
|
||||||
|
try
|
||||||
|
Some (Data_encoding.Json.destruct encoding (`String str))
|
||||||
|
with _ -> None
|
||||||
|
|
||||||
let init ?(template = default_template) kind =
|
let to_string output =
|
||||||
|
match Data_encoding.Json.construct encoding output with
|
||||||
|
| `String res -> res
|
||||||
|
| #Data_encoding.json -> assert false
|
||||||
|
|
||||||
|
let pp fmt output =
|
||||||
|
Format.fprintf fmt "%s" (to_string output)
|
||||||
|
end
|
||||||
|
|
||||||
|
let init ?(template = default_template) output =
|
||||||
|
let open Output in
|
||||||
begin
|
begin
|
||||||
match kind with
|
match output with
|
||||||
| Stderr ->
|
| Stderr ->
|
||||||
Lwt.return @@
|
Lwt.return @@
|
||||||
Lwt_log.channel ~template ~close_mode:`Keep ~channel:Lwt_io.stderr ()
|
Lwt_log.channel ~template ~close_mode:`Keep ~channel:Lwt_io.stderr ()
|
||||||
|
@ -69,13 +69,21 @@ val default_template : template
|
|||||||
|
|
||||||
val level_encoding : level Data_encoding.t
|
val level_encoding : level Data_encoding.t
|
||||||
|
|
||||||
type kind =
|
module Output : sig
|
||||||
| Null
|
type t =
|
||||||
| Stdout
|
| Null
|
||||||
| Stderr
|
| Stdout
|
||||||
| File of string
|
| Stderr
|
||||||
| Syslog of Lwt_log.syslog_facility
|
| File of string
|
||||||
|
| Syslog of Lwt_log.syslog_facility
|
||||||
|
|
||||||
val kind_encoding : kind Data_encoding.t
|
val encoding : t Data_encoding.t
|
||||||
|
val of_string : string -> t option
|
||||||
|
val to_string : t -> string
|
||||||
|
val pp : Format.formatter -> t -> unit
|
||||||
|
end
|
||||||
|
|
||||||
val init: ?template:template -> kind -> unit Lwt.t
|
|
||||||
|
val init: ?template:template -> Output.t -> unit Lwt.t
|
||||||
|
|
||||||
|
val sections : string list ref
|
||||||
|
@ -407,3 +407,70 @@ let with_timeout ?(canceler = Canceler.create ()) timeout f =
|
|||||||
let unless cond f =
|
let unless cond f =
|
||||||
if cond then Lwt.return () else f ()
|
if cond then Lwt.return () else f ()
|
||||||
|
|
||||||
|
module Lock_file = struct
|
||||||
|
let create_inner
|
||||||
|
lock_command
|
||||||
|
?(close_on_exec=true)
|
||||||
|
?(unlink_on_exit=false) fn =
|
||||||
|
protect begin fun () ->
|
||||||
|
Lwt_unix.openfile fn Unix.[O_CREAT ; O_WRONLY; O_TRUNC] 0o644 >>= fun fd ->
|
||||||
|
if close_on_exec then Lwt_unix.set_close_on_exec fd ;
|
||||||
|
Lwt_unix.lockf fd lock_command 0 >>= fun () ->
|
||||||
|
if unlink_on_exit then
|
||||||
|
Lwt_main.at_exit (fun () -> Lwt_unix.unlink fn) ;
|
||||||
|
let pid_str = string_of_int @@ Unix.getpid () in
|
||||||
|
Lwt_unix.write_string fd pid_str 0 (String.length pid_str) >>= fun _ ->
|
||||||
|
return ()
|
||||||
|
end
|
||||||
|
|
||||||
|
let create = create_inner Unix.F_TLOCK
|
||||||
|
|
||||||
|
let blocking_create
|
||||||
|
?timeout
|
||||||
|
?(close_on_exec=true)
|
||||||
|
?(unlink_on_exit=false) fn =
|
||||||
|
let create () =
|
||||||
|
create_inner Unix.F_LOCK ~close_on_exec ~unlink_on_exit fn in
|
||||||
|
match timeout with
|
||||||
|
| None -> create ()
|
||||||
|
| Some duration -> with_timeout duration (fun _ -> create ())
|
||||||
|
|
||||||
|
let is_locked fn =
|
||||||
|
if not @@ Sys.file_exists fn then return false else
|
||||||
|
protect begin fun () ->
|
||||||
|
Lwt_unix.openfile fn [Unix.O_RDONLY] 0o644 >>= fun fd ->
|
||||||
|
Lwt.finalize (fun () ->
|
||||||
|
Lwt.try_bind
|
||||||
|
(fun () -> Lwt_unix.(lockf fd F_TEST 0))
|
||||||
|
(fun () -> return false)
|
||||||
|
(fun _ -> return true))
|
||||||
|
(fun () -> Lwt_unix.close fd)
|
||||||
|
end
|
||||||
|
|
||||||
|
let get_pid fn =
|
||||||
|
let open Lwt_io in
|
||||||
|
protect begin fun () ->
|
||||||
|
with_file ~mode:Input fn begin fun ic ->
|
||||||
|
read ic >>= fun content ->
|
||||||
|
return (int_of_string content)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let of_sockaddr = function
|
||||||
|
| Unix.ADDR_UNIX _ -> None
|
||||||
|
| Unix.ADDR_INET (addr, port) ->
|
||||||
|
match Ipaddr_unix.of_inet_addr addr with
|
||||||
|
| V4 addr -> Some (Ipaddr.v6_of_v4 addr, port)
|
||||||
|
| V6 addr -> Some (addr, port)
|
||||||
|
|
||||||
|
let getaddrinfo ~passive ~node ~service =
|
||||||
|
let open Lwt_unix in
|
||||||
|
getaddrinfo node service
|
||||||
|
( AI_SOCKTYPE SOCK_STREAM ::
|
||||||
|
(if passive then [AI_PASSIVE] else []) ) >>= fun addr ->
|
||||||
|
let points =
|
||||||
|
Utils.filter_map
|
||||||
|
(fun { ai_addr } -> of_sockaddr ai_addr)
|
||||||
|
addr in
|
||||||
|
Lwt.return points
|
||||||
|
@ -69,3 +69,23 @@ val with_timeout:
|
|||||||
|
|
||||||
val unless: bool -> (unit -> unit Lwt.t) -> unit Lwt.t
|
val unless: bool -> (unit -> unit Lwt.t) -> unit Lwt.t
|
||||||
|
|
||||||
|
module Lock_file : sig
|
||||||
|
val create :
|
||||||
|
?close_on_exec:bool ->
|
||||||
|
?unlink_on_exit:bool ->
|
||||||
|
string -> unit tzresult Lwt.t
|
||||||
|
|
||||||
|
val blocking_create :
|
||||||
|
?timeout:float ->
|
||||||
|
?close_on_exec:bool ->
|
||||||
|
?unlink_on_exit:bool ->
|
||||||
|
string -> unit tzresult Lwt.t
|
||||||
|
|
||||||
|
val is_locked : string -> bool tzresult Lwt.t
|
||||||
|
val get_pid : string -> int tzresult Lwt.t
|
||||||
|
end
|
||||||
|
|
||||||
|
val getaddrinfo:
|
||||||
|
passive:bool ->
|
||||||
|
node:string -> service:string ->
|
||||||
|
(Ipaddr.V6.t * int) list Lwt.t
|
||||||
|
@ -2,7 +2,7 @@ COMMAND='gnome-terminal'
|
|||||||
COUNT=2
|
COUNT=2
|
||||||
for i in $(seq 1 $COUNT)
|
for i in $(seq 1 $COUNT)
|
||||||
do
|
do
|
||||||
SUBCOMMAND="./tezos-node --net-addr :::$((9900 + i)) --local-discovery :::7732 --rpc-addr :::$((8800 + i)) --expected-connections $(($COUNT - 1)) --base-dir /tmp/tezos_$i"
|
SUBCOMMAND="./tezos-node --net-addr :::$((9900 + i)) --local-discovery :::7732 --rpc-addr :::$((8800 + i)) --expected-connections $(($COUNT - 1)) --data-dir /tmp/tezos_$i"
|
||||||
COMMAND="$COMMAND --tab -e '$SUBCOMMAND'"
|
COMMAND="$COMMAND --tab -e '$SUBCOMMAND'"
|
||||||
done
|
done
|
||||||
echo $COMMAND
|
echo $COMMAND
|
||||||
|
@ -18,7 +18,7 @@ NODE=../tezos-node
|
|||||||
CLIENT="../tezos-client -base-dir ${CLIENT_DIR}"
|
CLIENT="../tezos-client -base-dir ${CLIENT_DIR}"
|
||||||
|
|
||||||
CUSTOM_PARAM="--sandbox ./sandbox.json"
|
CUSTOM_PARAM="--sandbox ./sandbox.json"
|
||||||
${NODE} --base-dir "${DATA_DIR}" ${CUSTOM_PARAM} --rpc-addr :::8732 > LOG 2>&1 &
|
${NODE} run --data-dir "${DATA_DIR}" ${CUSTOM_PARAM} --rpc-addr "[::]:8732" > LOG 2>&1 &
|
||||||
NODE_PID="$!"
|
NODE_PID="$!"
|
||||||
|
|
||||||
sleep 3
|
sleep 3
|
||||||
|
@ -50,9 +50,10 @@ let fork_node () =
|
|||||||
Unix.create_process
|
Unix.create_process
|
||||||
Filename.(concat (dirname (Sys.getcwd ())) "tezos-node")
|
Filename.(concat (dirname (Sys.getcwd ())) "tezos-node")
|
||||||
[| "tezos-node" ;
|
[| "tezos-node" ;
|
||||||
"--base-dir"; data_dir ;
|
"run" ;
|
||||||
|
"--data-dir"; data_dir ;
|
||||||
"--sandbox"; "./sandbox.json";
|
"--sandbox"; "./sandbox.json";
|
||||||
"--rpc-addr"; ":::8732" |]
|
"--rpc-addr"; "[::]:8732" |]
|
||||||
null_fd log_fd log_fd in
|
null_fd log_fd log_fd in
|
||||||
Printf.printf "Created node, pid: %d, log: %s\n%!" pid log_file_name ;
|
Printf.printf "Created node, pid: %d, log: %s\n%!" pid log_file_name ;
|
||||||
at_exit
|
at_exit
|
||||||
|
Loading…
Reference in New Issue
Block a user