123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2018 Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)(* Copyright (c) 2019-2022 Nomadic Labs, <contact@nomadic-labs.com> *)(* *)(* Permission is hereby granted, free of charge, to any person obtaining a *)(* copy of this software and associated documentation files (the "Software"),*)(* to deal in the Software without restriction, including without limitation *)(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *)(* and/or sell copies of the Software, and to permit persons to whom the *)(* Software is furnished to do so, subject to the following conditions: *)(* *)(* The above copyright notice and this permission notice shall be included *)(* in all copies or substantial portions of the Software. *)(* *)(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *)(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *)(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *)(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *)(* DEALINGS IN THE SOFTWARE. *)(* *)(*****************************************************************************)openFilename.Infixletstore_dirdata_dir=data_dir//"store"letcontext_dirdata_dir=data_dir//"context"letprotocol_dirdata_dir=data_dir//"protocol"letlock_filedata_dir=data_dir//"lock"letdefault_identity_file_name="identity.json"letdefault_peers_file_name="peers.json"letdefault_config_file_name="config.json"letversion_file_name="version.json"moduleVersion=structtypet={major:int;minor:int}letcomparev1v2=letc=Int.comparev1.majorv2.majorinifc<>0thencelseInt.comparev1.minorv2.minorlet(<)v1v2=comparev1v2<0letequalv1v2=comparev1v2=0letmake~major~minor=ifCompare.Int.(major<0||minor<0)theninvalid_arg(Printf.sprintf"Version.make: version number cannot be negative: %d.%d"majorminor);{major;minor}letparse_version_items=matchint_of_string_optswith|None->invalid_arg("Version.parse_version_item: invalid integer: "^String.escapeds)|Somei->iletof_strings=matchString.split_on_char'.'swith|[major;minor]->make~major:(parse_version_itemmajor)~minor:(parse_version_itemminor)|[_major;minor;patch]->(* Allows a backward compatibility from the previous version schema,
represented by a triplet. It transforms version X.Y.Z to Y.Z.
Note that there were no version with X <> 0. *)make~major:(parse_version_itemminor)~minor:(parse_version_itempatch)|_->invalid_arg"Version.of_string: string is not of the form X.Y"letto_string{major;minor}=Printf.sprintf"%d.%d"majorminorletppfmtv=Format.pp_print_stringfmt(to_stringv)letencoding=letopenData_encodinginconvto_stringof_string(obj1(req"version"string))end(* Data_version history:
* - (0.)0.1 : original storage
* - (0.)0.2 : never released
* - (0.)0.3 : store upgrade (introducing history mode)
* - (0.)0.4 : context upgrade (switching from LMDB to IRMIN v2)
* - (0.)0.5 : never released (but used in 10.0~rc1 and 10.0~rc2)
* - (0.)0.6 : store upgrade (switching from LMDB)
* - (0.)0.7 : new store metadata representation
* - (0.)0.8 : context upgrade (upgrade to irmin.3.0)
* - 1.0 : context upgrade (upgrade to irmin.3.3)
* - 2.0 : introduce context GC (upgrade to irmin.3.4)
* - 3.0 : change blocks' context hash semantics and upgrade to
irmin.3.5 *)(* FIXME https://gitlab.com/tezos/tezos/-/issues/2861
We should enable the semantic versioning instead of applying
hardcoded rules.*)letv_0_6=Version.make~major:0~minor:6letv_0_7=Version.make~major:0~minor:7letv_0_8=Version.make~major:0~minor:8letv_1_0=Version.make~major:1~minor:0letv_2_0=Version.make~major:2~minor:0letv_3_0=Version.make~major:3~minor:0letcurrent_version=v_3_0(* List of upgrade functions from each still supported previous
version to the current [data_version] above. If this list grows too
much, an idea would be to have triples (version, version,
converter), and to sequence them dynamically instead of
statically. *)letupgradable_data_version=letopenLwt_result_syntaxinletv_1_0_upgrade~data_dir=letcontext_root=context_dirdata_dirin(* The upgrade function consist in letting irmin doing its own
file renaming. To do so, it must be done using a RW instance.*)let*!ctxt=Context.init~readonly:falsecontext_rootinlet*!()=Context.closectxtinreturn_unitinletv_3_0_upgrade~data_dirgenesis=letstore_dir=store_dirdata_dirinStore.v_3_0_upgrade~store_dirgenesisin[(v_0_6,fun~data_dirgenesis~chain_name:_~sandbox_parameters:_->let*()=v_1_0_upgrade~data_dirinv_3_0_upgrade~data_dirgenesis);(v_0_7,fun~data_dirgenesis~chain_name:_~sandbox_parameters:_->let*()=v_1_0_upgrade~data_dirinv_3_0_upgrade~data_dirgenesis);(v_0_8,fun~data_dirgenesis~chain_name:_~sandbox_parameters:_->let*()=v_1_0_upgrade~data_dirinv_3_0_upgrade~data_dirgenesis);(v_1_0,fun~data_dirgenesis~chain_name:_~sandbox_parameters:_->v_3_0_upgrade~data_dirgenesis);(v_2_0,fun~data_dirgenesis~chain_name:_~sandbox_parameters:_->v_3_0_upgrade~data_dirgenesis);]typeerror+=Invalid_data_dir_versionofVersion.t*Version.ttypeerror+=Invalid_data_dirof{data_dir:string;msg:stringoption}typeerror+=Could_not_read_data_dir_versionofstringtypeerror+=Could_not_write_version_fileofstringtypeerror+=|Data_dir_needs_upgradeof{expected:Version.t;actual:Version.t}let()=register_error_kind`Permanent~id:"main.data_version.invalid_data_dir_version"~title:"Invalid data directory version"~description:"The data directory version was not the one that was expected"~pp:(funppf(exp,got)->Format.fprintfppf"Invalid data directory version '%a' (expected '%a').@,\
Your data directory is %s"Version.ppgotVersion.ppexp(ifVersion.comparegotexp<0then"incompatible and cannot be automatically upgraded."else"too recent for this node version."))Data_encoding.(obj2(req"expected_version"Version.encoding)(req"actual_version"Version.encoding))(function|Invalid_data_dir_version(expected,actual)->Some(expected,actual)|_->None)(fun(expected,actual)->Invalid_data_dir_version(expected,actual));register_error_kind`Permanent~id:"main.data_version.invalid_data_dir"~title:"Invalid data directory"~description:"The data directory cannot be accessed or created"~pp:(funppf(dir,msg_opt)->Format.fprintfppf"Invalid data directory '%s'%a."dir(Format.pp_print_option(funfmtmsg->Format.fprintffmt": %s"msg))msg_opt)Data_encoding.(obj2(req"datadir_path"string)(opt"message"string))(function|Invalid_data_dir{data_dir;msg}->Some(data_dir,msg)|_->None)(fun(data_dir,msg)->Invalid_data_dir{data_dir;msg});register_error_kind`Permanent~id:"main.data_version.could_not_read_data_dir_version"~title:"Could not read data directory version file"~description:"Data directory version file was invalid."Data_encoding.(obj1(req"version_path"string))~pp:(funppfpath->Format.fprintfppf"Tried to read version file at '%s', but the file could not be parsed."path)(functionCould_not_read_data_dir_versionpath->Somepath|_->None)(funpath->Could_not_read_data_dir_versionpath);register_error_kind`Permanent~id:"main.data_version.could_not_write_version_file"~title:"Could not write version file"~description:"Version file cannot be written."Data_encoding.(obj1(req"file_path"string))~pp:(funppffile_path->Format.fprintfppf"Tried to write version file at '%s', but the file could not be \
written."file_path)(function|Could_not_write_version_filefile_path->Somefile_path|_->None)(funfile_path->Could_not_write_version_filefile_path);register_error_kind`Permanent~id:"main.data_version.data_dir_needs_upgrade"~title:"The data directory needs to be upgraded"~description:"The data directory needs to be upgraded"~pp:(funppf(exp,got)->Format.fprintfppf"The data directory version is too old.@,\
Found '%a', expected '%a'.@,\
It needs to be upgraded with `octez-node upgrade storage`."Version.ppgotVersion.ppexp)Data_encoding.(obj2(req"expected_version"Version.encoding)(req"actual_version"Version.encoding))(function|Data_dir_needs_upgrade{expected;actual}->Some(expected,actual)|_->None)(fun(expected,actual)->Data_dir_needs_upgrade{expected;actual})moduleEvents=structopenInternal_event.Simpleletsection=["node";"data_version"]letdir_is_up_to_date=declare_0~section~level:Notice~name:"dir_is_up_to_date"~msg:"node data dir is up-to-date"()letupgrading_node=declare_2~section~level:Notice~name:"upgrading_node"~msg:"upgrading data directory from {old_version} to {new_version}"~pp1:Version.pp("old_version",Version.encoding)~pp2:Version.pp("new_version",Version.encoding)letfinished_upgrading_node=declare_2~section~level:Notice~name:"finished_upgrading_node"~msg:"the node's data directory was automatically upgraded from \
{old_version} to {new_version} and is now up-to-date"~pp1:Version.pp("old_version",Version.encoding)~pp2:Version.pp("new_version",Version.encoding)letupdate_success=declare_0~section~level:Notice~name:"update_success"~msg:"the node data dir is now up-to-date"()letaborting_upgrade=declare_1~section~level:Notice~name:"aborting_upgrade"~msg:"failed to upgrade storage: {error}"~pp1:Error_monad.pp_print_trace("error",Error_monad.trace_encoding)letupgrade_status=declare_2~section~level:Notice~name:"upgrade_status"~msg:"current version: {current_version}, available version: \
{available_version}"~pp1:Version.pp("current_version",Version.encoding)~pp2:Version.pp("available_version",Version.encoding)letemit=emitendletversion_filedata_dir=Filename.concatdata_dirversion_file_nameletclean_directoryfiles=letto_delete=Format.asprintf"%a"(Format.pp_print_list~pp_sep:(funfmt()->Format.fprintffmt", ")Format.pp_print_string)filesinFormat.sprintf"Please provide a clean directory by removing the following files: %s"to_deleteletwrite_version_filedata_dir=letversion_file=version_filedata_dirinLwt_utils_unix.Json.write_fileversion_file(Data_encoding.Json.constructVersion.encodingcurrent_version)|>trace(Could_not_write_version_fileversion_file)letread_version_fileversion_file=letopenLwt_result_syntaxinlet*json=trace(Could_not_read_data_dir_versionversion_file)(Lwt_utils_unix.Json.read_fileversion_file)intryreturn(Data_encoding.Json.destructVersion.encodingjson)with_->tzfail(Could_not_read_data_dir_versionversion_file)letcheck_data_dir_versionfilesdata_dir=letopenLwt_result_syntaxinletversion_file=version_filedata_dirinlet*!file_exists=Lwt_unix.file_existsversion_fileinifnotfile_existsthenletmsg=Some(clean_directoryfiles)intzfail(Invalid_data_dir{data_dir;msg})elselet*version=read_version_fileversion_fileinifVersion.(equalversioncurrent_version)thenreturn_noneelsematchList.find_opt(fun(v,_)->Version.equalvversion)upgradable_data_versionwith|Somef->return_somef|None->tzfail(Invalid_data_dir_version(current_version,version))typeensure_mode=Exists|Is_bare|Is_compatibleletensure_data_dir~modedata_dir=letopenLwt_result_syntaxinletwrite_version()=let*()=write_version_filedata_dirinreturn_noneinLwt.catch(fun()->let*!file_exists=Lwt_unix.file_existsdata_diriniffile_existsthenlet*!files=Lwt_stream.to_list(Lwt_unix.files_of_directorydata_dir)inletfiles=List.filter(funs->s<>"."&&s<>".."&&s<>version_file_name&&s<>default_identity_file_name&&s<>default_config_file_name&&s<>default_peers_file_name)filesinmatch(files,mode)with|[],_->write_version()|files,Is_bare->letmsg=Some(clean_directoryfiles)intzfail(Invalid_data_dir{data_dir;msg})|files,Is_compatible->check_data_dir_versionfilesdata_dir|_files,Exists->return_noneelselet*!()=Lwt_utils_unix.create_dir~perm:0o700data_dirinwrite_version())(function|Unix.Unix_error_->tzfail(Invalid_data_dir{data_dir;msg=None})|exc->raiseexc)letupgrade_data_dir~data_dirgenesis~chain_name~sandbox_parameters=letopenLwt_result_syntaxinlet*o=ensure_data_dir~mode:Is_compatibledata_dirinmatchowith|None->let*!()=Events.(emitdir_is_up_to_date())inreturn_unit|Some(version,upgrade)->(let*!()=Events.(emitupgrading_node(version,current_version))inlet*!r=upgrade~data_dirgenesis~chain_name~sandbox_parametersinmatchrwith|Ok_success_message->let*()=write_version_filedata_dirinlet*!()=Events.(emitupdate_success())inreturn_unit|Errore->let*!()=Events.(emitaborting_upgradee)inLwt.return(Errore))letensure_data_dir?(mode=Is_compatible)genesisdata_dir=letopenLwt_result_syntaxinlet*o=ensure_data_dir~modedata_dirinmatchowith|None->return_unit(* Enable automatic upgrade to avoid users to manually upgrade. *)|Some(version,_)whenVersion.(equalversionv_2_0||equalversionv_1_0||equalversionv_0_6||equalversionv_0_7||equalversionv_0_8)->let*()=upgrade_data_dir~data_dirgenesis~chain_name:()~sandbox_parameters:()inlet*!()=Events.(emitfinished_upgrading_node(version,current_version))inreturn_unit|Some(version,_)whenVersion.(version<current_version)->tzfail(Data_dir_needs_upgrade{expected=current_version;actual=version})|Some(version,_)->tzfail(Invalid_data_dir_version(current_version,version))letupgrade_statusdata_dir=letopenLwt_result_syntaxinlet*data_dir_version=read_version_file(version_filedata_dir)inlet*!()=Events.(emitupgrade_status(data_dir_version,current_version))inreturn_unit