123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2021 Nomadic Labs, <contact@nomadic-labs.com> *)(* *)(* Permission is hereby granted, free of charge, to any person obtaining a *)(* copy of this software and associated documentation files (the "Software"),*)(* to deal in the Software without restriction, including without limitation *)(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *)(* and/or sell copies of the Software, and to permit persons to whom the *)(* Software is furnished to do so, subject to the following conditions: *)(* *)(* The above copyright notice and this permission notice shall be included *)(* in all copies or substantial portions of the Software. *)(* *)(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *)(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *)(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *)(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *)(* DEALINGS IN THE SOFTWARE. *)(* *)(*****************************************************************************)(* Overlay for simple events that allows us to retrieve the level. *)letsection=["node";"config";"validation"]moduleE:sigtype'atvallevel:'at->Internal_event.levelvalevent:'at->'aInternal_event.Simple.tvaldeclare_0:name:string->msg:string->level:Internal_event.level->unit->unittvaldeclare_1:name:string->msg:string->level:Internal_event.level->string*'aData_encoding.t->'atvaldeclare_2:name:string->msg:string->level:Internal_event.level->string*'aData_encoding.t->string*'bData_encoding.t->('a*'b)tvaldeclare_3:name:string->msg:string->level:Internal_event.level->string*'aData_encoding.t->string*'bData_encoding.t->string*'cData_encoding.t->('a*'b*'c)tend=structtype'at={level:Internal_event.Level.t;event:'aInternal_event.Simple.t;}letlevel{level;_}=levelletevent{event;_}=eventletprefix_with_levellevelmsg=Format.sprintf"%s: %s"(Internal_event.Level.to_stringlevel)msgletdeclare_0~name~msg~levelx=letmsg=prefix_with_levellevelmsgin{level;event=Internal_event.Simple.declare_0~section~name~msg~levelx;}letdeclare_1~name~msg~levelx=letmsg=prefix_with_levellevelmsgin{level;event=Internal_event.Simple.declare_1~section~name~msg~levelx;}letdeclare_2~name~msg~levelxy=letmsg=prefix_with_levellevelmsgin{level;event=Internal_event.Simple.declare_2~section~name~msg~levelxy;}letdeclare_3~name~msg~levelxyz=letmsg=prefix_with_levellevelmsgin{level;event=Internal_event.Simple.declare_3~section~name~msg~levelxyz;}end(* The type for a node configuration warning/error. *)typealert=Alert:{event:'aE.t;payload:'a}->alertletis_error(Alert{event;_})=E.levelevent=Errorletis_warning(Alert{event;_})=E.levelevent=Warning(* Errors *)typeerror+=Invalid_node_configurationlet()=register_error_kind`Permanent~id:"config_validation.invalid_node_configuration"~title:"Invalid node configuration"~description:"The node configuration is invalid."~pp:(funppf()->Format.fprintfppf"The node configuration is invalid, use `%s config validate [options]`"Sys.argv.(0))Data_encoding.unit(functionInvalid_node_configuration->Some()|_->None)(fun()->Invalid_node_configuration)(* The type for a configuration validation report. *)typet=alertlistlethas_errort=List.existsis_errortlethas_warningt=List.existsis_warningtmoduleEvent=structopenInternal_event.Simpleletemit=emitletdisabled_event=declare_0~section~name:"config_validation_disabled"~msg:"the node configuration validation is disabled."~level:Notice()letsuccess_event=declare_0~section~name:"config_validation_success"~msg:"the node configuration has been successfully validated."~level:Notice()leterror_event=declare_0~section~name:"config_validation_error"~msg:"found the following error(s) while validating the node configuration."~level:Error()letwarning_event=declare_0~section~name:"config_validation_warning"~msg:"found the following warning(s) while validating the node \
configuration."~level:Warning()letemit_allt=Lwt_list.iter_s(functionAlert{event;payload}->emit(E.eventevent)payload)tletreportt=letopenLwt_syntaxinleterrors=List.filteris_errortinletwarnings=List.filteris_warningtinlet*()=matcherrorswith|[]->Lwt.return_unit|xs->let*()=emiterror_event()inemit_allxsinmatchwarningswith|[]->Lwt.return_unit|xs->let*()=emitwarning_event()inemit_allxsendletmk_alert~event~payload=Alert{event;payload}letwhen_condition~event~payload=ifnotconditionthen[]else[mk_alert~event~payload]letunlesscondition~event~payload=ifconditionthen[]else[mk_alert~event~payload](* The following parts consist in node configuration validations. *)(* Validate expected proof-of-work. *)letinvalid_pow=E.declare_1~name:"invalid_pow"~level:Error~msg:(Format.sprintf"the expected proof-of-work must be between 0 and 256 (inclusive), \
but found the value {proof-of-work} in field '%s'.""p2p.expected-proof-of-work")("proof-of-work",Data_encoding.float)letvalidate_expected_pow(config:Config_file.t):(t,'error)resultLwt.t=unless(0.<=config.p2p.expected_pow&&config.p2p.expected_pow<=256.)~event:invalid_pow~payload:config.p2p.expected_pow|>Lwt.return_ok(* Validate addresses. *)letcannot_parse_addr=E.declare_3~name:"cannot_parse_addr"~msg:"failed to parse address '{addr}' in field '{field}': {why}."~level:Error("addr",Data_encoding.string)("field",Data_encoding.string)("why",Data_encoding.string)letcannot_resolve_addr=E.declare_2~name:"cannot_resolve_addr"~msg:"failed to resolve address '{addr}' in field '{field}'."~level:Warning("addr",Data_encoding.string)("field",Data_encoding.string)letcannot_resolve_bootstrap_peer_addr=E.declare_2~name:"cannot_resolve_bootstrap_peer_addr"~msg:"failed to resolve the bootstrap peer address '{addr}' in field \
'{field}', the node will not use this bootstrap peer"~level:Warning("addr",Data_encoding.string)("field",Data_encoding.string)letvalidate_addr?e_resolve?e_parse~field~addrresolver=letopenLwt_result_syntaxinlet*!r=resolveraddrinmatchrwith|Error[P2p_resolve.Failed_to_parse_address(addr,why)]->return_some(mk_alert~event:(Option.valuee_parse~default:cannot_parse_addr)~payload:(addr,field,why))|Ok[]->return_some(mk_alert~event:(Option.valuee_resolve~default:cannot_resolve_addr)~payload:(addr,field))|Ok_->return_none|Error_ase->Lwt.returneletvalidate_addr_opt?e_resolve?e_parse~field~addrresolver=letaddr=Option.to_listaddrinList.filter_map_es(funaddr->validate_addr?e_resolve?e_parse~field~addrresolver)addrletvalidate_rpc_listening_addrs(config:Config_file.t)=letauxaddr=validate_addr~field:"rpc.listen-addrs"~addrConfig_file.resolve_rpc_listening_addrsinList.filter_map_epauxconfig.rpc.listen_addrsletvalidate_p2p_listening_addrs(config:Config_file.t)=validate_addr_opt~field:"p2p.listen-addr"~addr:config.p2p.listen_addrConfig_file.resolve_listening_addrsletvalidate_p2p_discovery_addr(config:Config_file.t)=validate_addr_opt~field:"p2p.discovery-addr"~addr:config.p2p.discovery_addrConfig_file.resolve_discovery_addrsletvalidate_p2p_bootstrap_addrs~fieldpeers=letauxaddr=validate_addr~e_resolve:cannot_resolve_bootstrap_peer_addr~field~addr(funx->Config_file.resolve_bootstrap_addrs[x])inList.filter_map_epauxpeersletvalidate_p2p_bootstrap_peers(config:Config_file.t)=matchconfig.p2p.bootstrap_peerswith|None->validate_p2p_bootstrap_addrs~field:"network.default_bootstrap-peers"config.blockchain_network.default_bootstrap_peers|Somepeers->validate_p2p_bootstrap_addrs~field:"p2p.bootstrap-peers"peersletvalidate_addressesconfig:ttzresultLwt.t=List.concat_map_es(funf->fconfig)[validate_rpc_listening_addrs;validate_p2p_bootstrap_peers;validate_p2p_listening_addrs;validate_p2p_discovery_addr;](* Validate connections setup. *)letconnections_min_expected=E.declare_2~name:"minimum_connections_greater_than_expected"~level:Error~msg:(Format.sprintf"the minimum number of connections found in field '%s' ({minimum}) is \
greater than the expected number of connections found in field '%s' \
({expected}).""p2p.limits.min-connections""p2p.limits.expected-connections")("minimum",Data_encoding.int16)("expected",Data_encoding.int16)letconnections_expected_max=E.declare_2~name:"expected_connections_greater_than_maximum"~level:Error~msg:(Format.sprintf"the expected number of connections found in field '%s' ({expected}) \
is greater than the maximum number of connections found in field \
'%s' ({maximum}).""p2p.limits.expected-connections""p2p.limits.max-connections")("expected",Data_encoding.int16)("maximum",Data_encoding.int16)lettarget_number_of_known_peers_greater_than_maximum=E.declare_2~name:"target_number_of_known_peers_greater_than_maximum"~level:Error~msg:(Format.sprintf"in field '%s', the target number of known peer ids ({target}) is \
greater than the maximum number of known peers ids ({maximum}).""p2p.limits.max_known_peer_ids")("target",Data_encoding.int16)("maximum",Data_encoding.int16)lettarget_number_of_known_peers_lower_than_maximum_conn=E.declare_2~name:"target_number_of_known_peers_greater_than_maximum_conn"~level:Error~msg:(Format.sprintf"the target number of known peer ids ({target}) in field '%s', is \
lower than the maximum number of connections ({maximum}) found in \
field '%s'.""p2p.limits.max_known_peer_ids""p2p.limits.max-connections")("target",Data_encoding.int16)("maximum",Data_encoding.int16)lettarget_number_of_known_points_greater_than_maximum=E.declare_2~name:"target_number_of_known_points_greater_than_maximum"~level:Error~msg:(Format.sprintf"in field '%s', the target number of known point ids ({target}) is \
greater than the maximum number of known points ids ({maximum}).""p2p.limits.max_known_points")("target",Data_encoding.int16)("maximum",Data_encoding.int16)lettarget_number_of_known_points_lower_than_maximum_conn=E.declare_2~name:"target_number_of_known_points_greater_than_maximum_conn"~level:Error~msg:(Format.sprintf"the target number of known point ids ({target}) found in field '%s' \
is lower than the maximum number of connections ({maximum}) found in \
'%s'.""p2p.limits.max_known_points""p2p.limits.max-connections")("target",Data_encoding.int16)("maximum",Data_encoding.int16)letvalidate_connections(config:Config_file.t)=letvalidated_connections=letlimits=config.p2p.limitsinwhen_(limits.min_connections>limits.expected_connections)~event:connections_min_expected~payload:(limits.min_connections,limits.expected_connections)@when_(limits.expected_connections>limits.max_connections)~event:connections_expected_max~payload:(limits.expected_connections,limits.max_connections)@Option.foldlimits.max_known_peer_ids~none:[]~some:(fun(max_known_peer_ids,target_known_peer_ids)->when_(target_known_peer_ids>max_known_peer_ids)~event:target_number_of_known_peers_greater_than_maximum~payload:(target_known_peer_ids,max_known_peer_ids)@when_(limits.max_connections>target_known_peer_ids)~event:target_number_of_known_peers_lower_than_maximum_conn~payload:(target_known_peer_ids,limits.max_connections))@Option.foldlimits.max_known_points~none:[]~some:(fun(max_known_points,target_known_points)->when_(target_known_points>max_known_points)~event:target_number_of_known_points_greater_than_maximum~payload:(max_known_points,target_known_points)@when_(limits.max_connections>target_known_points)~event:target_number_of_known_points_lower_than_maximum_conn~payload:(target_known_points,limits.max_connections))inLwt.return_okvalidated_connectionsletmaintenance_disabled=E.declare_0~name:"maintenance_disabled"~level:Warning~msg:"The P2P maintenance is disabled. The P2P maintenance should only be \
disabled for testing purposes."()letwarn_maintenance_deactivated(config:Config_file.t)=when_(config.p2p.limits.maintenance_idle_time=None)~event:maintenance_disabled~payload:()|>Lwt_result.return(* Deprecated argument *)lettestchain_is_deprecated=E.declare_0~level:Warning~name:"enable_testchain_is_deprecated_in_configuration_file"~msg:"The option `p2p.enable_testchain` is deprecated."()letwarn_deprecated_fields(config:Config_file.t)=when_config.p2p.enable_testchain~event:testchain_is_deprecated~payload:()|>Lwt_result.return(* Main validation passes. *)letvalidation_passesignore_testchain_warning=[validate_expected_pow;validate_addresses;validate_connections;warn_maintenance_deactivated;]@ifignore_testchain_warningthen[]else[warn_deprecated_fields]letvalidate_passes?(ignore_testchain_warning=false)config=List.concat_map_es(funf->fconfig)(validation_passesignore_testchain_warning)(* Main validation functions. *)letcheck?ignore_testchain_warningconfig=letopenLwt_result_syntaxinifconfig.Config_file.disable_config_validationthenlet*!()=Event.(emitdisabled_event())inreturn_unitelselet*t=validate_passes?ignore_testchain_warningconfiginifhas_errortthenlet*!()=Event.reporttintzfailInvalid_node_configurationelseifhas_warningtthenlet*!()=Event.reporttinreturn_unitelselet*!()=Event.(emitsuccess_event())inreturn_unit