123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2021 Nomadic Labs <contact@nomadic-labs.com> *)(* Copyright (c) 2022 TriliTech <contact@trili.tech> *)(* *)(* 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. *)(* *)(*****************************************************************************)typeerror+=|(* `Temporary *)Sc_rollup_already_staked|(* `Temporary *)Sc_rollup_disputed|(* `Temporary *)Sc_rollup_does_not_existofSc_rollup_repr.t|(* `Temporary *)Sc_rollup_no_conflict|(* `Temporary *)Sc_rollup_no_stakers|(* `Temporary *)Sc_rollup_not_staked|(* `Temporary *)Sc_rollup_not_staked_on_lcc|(* `Temporary *)Sc_rollup_parent_not_lcc|(* `Temporary *)Sc_rollup_remove_lcc|(* `Temporary *)Sc_rollup_staker_backtracked|(* `Temporary *)Sc_rollup_too_far_ahead|(* `Temporary *)Sc_rollup_too_recent|(* `Temporary *)Sc_rollup_unknown_commitmentofSc_rollup_repr.Commitment_hash.t|(* `Temporary *)Sc_rollup_bad_inbox_level|(* `Temporary *)Sc_rollup_max_number_of_available_messages_reachedlet()=register_error_kind`Temporary~id:"Sc_rollup_max_number_of_available_messages_reached"~title:"Maximum number of available messages reached"~description:"Maximum number of available messages reached"Data_encoding.unit(function|Sc_rollup_max_number_of_available_messages_reached->Some()|_->None)(fun()->Sc_rollup_max_number_of_available_messages_reached);letdescription="Already staked."inregister_error_kind`Temporary~id:"Sc_rollup_already_staked"~title:"Already staked"~description~pp:(funppf()->Format.fprintfppf"%s"description)Data_encoding.empty(functionSc_rollup_already_staked->Some()|_->None)(fun()->Sc_rollup_already_staked);letdescription="Attempted to cement a disputed commitment."inregister_error_kind`Temporary~id:"Sc_rollup_disputed"~title:"Commitment disputed"~description~pp:(funppf()->Format.fprintfppf"%s"description)Data_encoding.empty(functionSc_rollup_disputed->Some()|_->None)(fun()->Sc_rollup_disputed);letdescription="Attempted to use a rollup that has not been originated."inregister_error_kind`Temporary~id:"Sc_rollup_does_not_exist"~title:"Rollup does not exist"~description~pp:(funppfx->Format.fprintfppf"Rollup %a does not exist"Sc_rollup_repr.ppx)Data_encoding.(obj1(req"rollup"Sc_rollup_repr.encoding))(functionSc_rollup_does_not_existx->Somex|_->None)(funx->Sc_rollup_does_not_existx);letdescription="No conflict."inregister_error_kind`Temporary~id:"Sc_rollup_no_conflict"~title:"No conflict"~description~pp:(funppf()->Format.fprintfppf"%s"description)Data_encoding.empty(functionSc_rollup_no_conflict->Some()|_->None)(fun()->Sc_rollup_no_conflict);letdescription="No stakers."inregister_error_kind`Temporary~id:"Sc_rollup_no_stakers"~title:"No stakers"~description~pp:(funppf()->Format.fprintfppf"%s"description)Data_encoding.empty(functionSc_rollup_no_stakers->Some()|_->None)(fun()->Sc_rollup_no_stakers);letdescription="Unknown staker."inregister_error_kind`Temporary~id:"Sc_rollup_not_staked"~title:"Unknown staker"~description~pp:(funppf()->Format.fprintfppf"%s"description)Data_encoding.empty(functionSc_rollup_not_staked->Some()|_->None)(fun()->Sc_rollup_not_staked);letdescription="Attempted to withdraw while not staked on the last cemented commitment."inregister_error_kind`Temporary~id:"Sc_rollup_not_staked_on_lcc"~title:"Rollup not staked on LCC"~description~pp:(funppf()->Format.fprintfppf"%s"description)Data_encoding.empty(functionSc_rollup_not_staked_on_lcc->Some()|_->None)(fun()->Sc_rollup_not_staked_on_lcc);letdescription="Parent is not cemented."inregister_error_kind`Temporary~id:"Sc_rollup_parent_not_lcc"~title:"Parent not cemented"~description~pp:(funppf()->Format.fprintfppf"%s"description)Data_encoding.empty(functionSc_rollup_parent_not_lcc->Some()|_->None)(fun()->Sc_rollup_parent_not_lcc);letdescription="Can not remove a cemented commitment."inregister_error_kind`Temporary~id:"Sc_rollup_remove_lcc"~title:"Can not remove cemented"~description~pp:(funppf()->Format.fprintfppf"%s"description)Data_encoding.empty(functionSc_rollup_remove_lcc->Some()|_->None)(fun()->Sc_rollup_remove_lcc);letdescription="Staker backtracked."inregister_error_kind`Temporary~id:"Sc_rollup_staker_backtracked"~title:"Staker backtracked"~description~pp:(funppf()->Format.fprintfppf"%s"description)Data_encoding.empty(functionSc_rollup_staker_backtracked->Some()|_->None)(fun()->Sc_rollup_staker_backtracked);letdescription="Commitment is too far ahead of the last cemented commitment."inregister_error_kind`Temporary~id:"Sc_rollup_too_far_ahead"~title:"Commitment too far ahead"~description~pp:(funppf()->Format.fprintfppf"%s"description)Data_encoding.empty(functionSc_rollup_too_far_ahead->Some()|_->None)(fun()->Sc_rollup_too_far_ahead);letdescription="Attempted to cement a commitment before its refutation deadline."inregister_error_kind`Temporary~id:"Sc_rollup_too_recent"~title:"Commitment too recent"~description~pp:(funppf()->Format.fprintfppf"%s"description)Data_encoding.empty(functionSc_rollup_too_recent->Some()|_->None)(fun()->Sc_rollup_too_recent);letdescription="Unknown commitment."inregister_error_kind`Temporary~id:"Sc_rollup_unknown_commitment"~title:"Rollup does not exist"~description~pp:(funppfx->Format.fprintfppf"Commitment %a does not exist"Sc_rollup_repr.Commitment_hash.ppx)Data_encoding.(obj1(req"commitment"Sc_rollup_repr.Commitment_hash.encoding))(functionSc_rollup_unknown_commitmentx->Somex|_->None)(funx->Sc_rollup_unknown_commitmentx);letdescription="Attempted to commit to a bad inbox level."inregister_error_kind`Temporary~id:"Sc_rollup_bad_inbox_level"~title:"Committed too soon"~description~pp:(funppf()->Format.fprintfppf"%s"description)Data_encoding.empty(functionSc_rollup_bad_inbox_level->Some()|_->None)(fun()->Sc_rollup_bad_inbox_level);()(** To be removed once [Lwt_tzresult_syntax] is in the environment. *)moduleLwt_tzresult_syntax=structlet(let*)=(>>=?)letreturn=returnendmoduleStore=Storage.Sc_rollupmoduleCommitment=Sc_rollup_repr.CommitmentmoduleCommitment_hash=Sc_rollup_repr.Commitment_hashletoriginatectxt~kind~boot_sector=Raw_context.increment_origination_noncectxt>>?=fun(ctxt,nonce)->letlevel=Raw_context.current_levelctxtinSc_rollup_repr.Address.from_noncenonce>>?=funaddress->Storage.Sc_rollup.PVM_kind.addctxtaddresskind>>=functxt->Storage.Sc_rollup.Initial_level.addctxtaddress(Level_storage.currentctxt).level>>=functxt->Storage.Sc_rollup.Boot_sector.addctxtaddressboot_sector>>=functxt->letinbox=Sc_rollup_inbox_repr.emptyaddresslevel.levelinStorage.Sc_rollup.Inbox.initctxtaddressinbox>>=?fun(ctxt,size_diff)->Store.Last_cemented_commitment.initctxtaddressCommitment_hash.zero>>=?fun(ctxt,lcc_size_diff)->Store.Staker_count.initctxtaddress0l>>=?fun(ctxt,stakers_size_diff)->letaddresses_size=2*Sc_rollup_repr.Address.sizeinletstored_kind_size=2(* because tag_size of kind encoding is 16bits. *)inletboot_sector_size=Data_encoding.Binary.lengthData_encoding.stringboot_sectorinletorigination_size=Constants_storage.sc_rollup_origination_sizectxtinletsize=Z.of_int(origination_size+stored_kind_size+boot_sector_size+addresses_size+size_diff+lcc_size_diff+stakers_size_diff)inreturn(address,size,ctxt)letkindctxtaddress=Storage.Sc_rollup.PVM_kind.findctxtaddressletlast_cemented_commitmentctxtrollup=letopenLwt_tzresult_syntaxinlet*(ctxt,res)=Store.Last_cemented_commitment.findctxtrollupinmatchreswith|None->fail(Sc_rollup_does_not_existrollup)|Somelcc->return(lcc,ctxt)(** Try to consume n messages. *)letconsume_n_messagesctxtrollupn=letopenLwt_tzresult_syntaxinlet*(ctxt,inbox)=Storage.Sc_rollup.Inbox.getctxtrollupinSc_rollup_inbox_repr.consume_n_messagesninbox>>?=function|None->returnctxt|Someinbox->let*(ctxt,size)=Storage.Sc_rollup.Inbox.updatectxtrollupinboxinassert(Compare.Int.(size<=0));returnctxtletinboxctxtrollup=letopenLwt_tzresult_syntaxinlet*(ctxt,res)=Storage.Sc_rollup.Inbox.findctxtrollupinmatchreswith|None->fail(Sc_rollup_does_not_existrollup)|Someinbox->return(inbox,ctxt)letassert_inbox_size_okctxtnext_size=letmax_size=Constants_storage.sc_rollup_max_available_messagesctxtinfail_unlessCompare.Z.(next_size<=Z.of_intmax_size)Sc_rollup_max_number_of_available_messages_reachedletadd_messagesctxtrollupmessages=letopenRaw_contextininboxctxtrollup>>=?fun(inbox,ctxt)->letnext_size=Z.add(Sc_rollup_inbox_repr.number_of_available_messagesinbox)(Z.of_int(List.lengthmessages))inassert_inbox_size_okctxtnext_size>>=?fun()->Sc_rollup_in_memory_inbox.current_messagesctxtrollup|>funcurrent_messages->let{Level_repr.level;_}=Raw_context.current_levelctxtin(*
Notice that the protocol is forgetful: it throws away the inbox
history. On the contrary, the history is stored by the rollup
node to produce inclusion proofs when needed.
*)Sc_rollup_inbox_repr.(add_messages_no_historyinboxlevelmessagescurrent_messages)>>=?fun(current_messages,inbox)->Sc_rollup_in_memory_inbox.set_current_messagesctxtrollupcurrent_messages|>functxt->Storage.Sc_rollup.Inbox.updatectxtrollupinbox>>=?fun(ctxt,size)->return(inbox,Z.of_intsize,ctxt)(* This function is called in other functions in the module only after they have
checked for the existence of the rollup, and therefore it is not necessary
for it to check for the existence of the rollup again. It is not directly
exposed by the module. Instead, a different public function [get_commitment]
is provided, which checks for the existence of [rollup] before calling
[get_commitment_internal]. *)letget_commitment_internalctxtrollupcommitment=letopenLwt_tzresult_syntaxinlet*(ctxt,res)=Store.Commitments.find(ctxt,rollup)commitmentinmatchreswith|None->fail(Sc_rollup_unknown_commitmentcommitment)|Somecommitment->return(commitment,ctxt)letget_commitmentctxtrollupcommitment=letopenLwt_tzresult_syntaxin(* Assert that a last cemented commitment exists. *)let*(_lcc,ctxt)=last_cemented_commitmentctxtrollupinget_commitment_internalctxtrollupcommitmentletget_predecessorctxtrollupnode=letopenLwt_tzresult_syntaxinlet*(commitment,ctxt)=get_commitment_internalctxtrollupnodeinreturn(commitment.predecessor,ctxt)letfind_stakerctxtrollupstaker=letopenLwt_tzresult_syntaxinlet*(ctxt,res)=Store.Stakers.find(ctxt,rollup)stakerinmatchreswith|None->failSc_rollup_not_staked|Somebranch->return(branch,ctxt)letmodify_staker_countctxtrollupf=letopenLwt_tzresult_syntaxinlet*(ctxt,maybe_count)=Store.Staker_count.findctxtrollupinletcount=Option.value~default:0lmaybe_countinlet*(ctxt,size_diff,_was_bound)=Store.Staker_count.addctxtrollup(fcount)inassert(Compare.Int.(size_diff=0));returnctxtletget_commitment_stake_countctxtrollupnode=letopenLwt_tzresult_syntaxinlet*(ctxt,maybe_staked_on_commitment)=Store.Commitment_stake_count.find(ctxt,rollup)nodeinreturn(Option.value~default:0lmaybe_staked_on_commitment,ctxt)(** [set_commitment_added ctxt rollup node current] sets the commitment
addition time of [node] to [current] iff the commitment time was
not previously set, and leaves it unchanged otherwise.
*)letset_commitment_addedctxtrollupnodenew_value=letopenLwt_tzresult_syntaxinlet*(ctxt,res)=Store.Commitment_added.find(ctxt,rollup)nodeinletnew_value=matchreswithNone->new_value|Someold_value->old_valueinlet*(ctxt,size_diff,_was_bound)=Store.Commitment_added.add(ctxt,rollup)nodenew_valueinreturn(size_diff,ctxt)letdeallocatectxtrollupnode=letopenLwt_tzresult_syntaxinifCommitment_hash.(node=zero)thenreturnctxtelselet*(ctxt,_size_freed)=Store.Commitments.remove_existing(ctxt,rollup)nodeinlet*(ctxt,_size_freed)=Store.Commitment_added.remove_existing(ctxt,rollup)nodeinlet*(ctxt,_size_freed)=Store.Commitment_stake_count.remove_existing(ctxt,rollup)nodeinreturnctxtletmodify_commitment_stake_countctxtrollupnodef=letopenLwt_tzresult_syntaxinlet*(count,ctxt)=get_commitment_stake_countctxtrollupnodeinletnew_count=fcountinlet*(ctxt,size_diff,_was_bound)=Store.Commitment_stake_count.add(ctxt,rollup)nodenew_countinreturn(new_count,size_diff,ctxt)letincrease_commitment_stake_countctxtrollupnode=letopenLwt_tzresult_syntaxinlet*(_new_count,size_diff,ctxt)=modify_commitment_stake_countctxtrollupnodeInt32.succinreturn(size_diff,ctxt)letdecrease_commitment_stake_countctxtrollupnode=letopenLwt_tzresult_syntaxinlet*(new_count,_size_diff,ctxt)=modify_commitment_stake_countctxtrollupnodeInt32.predinifCompare.Int32.(new_count<=0l)thendeallocatectxtrollupnodeelsereturnctxtletdeposit_stakectxtrollupstaker=letopenLwt_tzresult_syntaxinlet*(lcc,ctxt)=last_cemented_commitmentctxtrollupinlet*(ctxt,res)=Store.Stakers.find(ctxt,rollup)stakerinmatchreswith|None->(* TODO: https://gitlab.com/tezos/tezos/-/issues/2449
We should lock stake here, and fail if there aren't enough funds.
*)let*(ctxt,_size)=Store.Stakers.init(ctxt,rollup)stakerlccinlet*ctxt=modify_staker_countctxtrollupInt32.succinreturnctxt|Some_->failSc_rollup_already_stakedletwithdraw_stakectxtrollupstaker=letopenLwt_tzresult_syntaxinlet*(lcc,ctxt)=last_cemented_commitmentctxtrollupinlet*(ctxt,res)=Store.Stakers.find(ctxt,rollup)stakerinmatchreswith|None->failSc_rollup_not_staked|Somestaked_on_commitment->ifCommitment_hash.(staked_on_commitment=lcc)then(* TODO: https://gitlab.com/tezos/tezos/-/issues/2449
We should refund stake here.
*)let*(ctxt,_size_freed)=Store.Stakers.remove_existing(ctxt,rollup)stakerinlet*ctxt=modify_staker_countctxtrollupInt32.predinmodify_staker_countctxtrollupInt32.predelsefailSc_rollup_not_staked_on_lcc(* TODO https://gitlab.com/tezos/tezos/-/issues/2548
These should be protocol constants. See issue for invariant that should be
tested for. *)letsc_rollup_max_lookahead=30_000lletsc_rollup_commitment_frequency=(* Think twice before changing this. And then avoid doing it. *)20(* 76 for Commitments entry + 4 for Commitment_stake_count entry + 4 for Commitment_added entry *)letsc_rollup_commitment_storage_size_in_bytes=84letassert_commitment_not_too_far_aheadctxtrolluplcccommitment=letopenLwt_tzresult_syntaxinlet*(ctxt,min_level)=ifCommitment_hash.(lcc=zero)thenlet*level=Store.Initial_level.getctxtrollupinreturn(ctxt,level)elselet*(lcc,ctxt)=get_commitment_internalctxtrolluplccinreturn(ctxt,Commitment.(lcc.inbox_level))inletmax_level=Commitment.(commitment.inbox_level)inifCompare.Int32.(sc_rollup_max_lookahead<Raw_level_repr.diffmax_levelmin_level)thenfailSc_rollup_too_far_aheadelsereturnctxt(** Enfore that a commitment's inbox level increases by an exact fixed amount over its predecessor.
This property is used in several places - not obeying it causes severe breakage.
*)letassert_commitment_frequencyctxtrollupcommitment=letopenLwt_tzresult_syntaxinletpred=Commitment.(commitment.predecessor)inlet*(ctxt,pred_level)=ifCommitment_hash.(pred=zero)thenlet*level=Store.Initial_level.getctxtrollupinreturn(ctxt,level)elselet*(pred,ctxt)=get_commitment_internalctxtrollupcommitment.predecessorinreturn(ctxt,Commitment.(pred.inbox_level))in(* We want to check the following inequalities on [commitment.inbox_level],
[commitment.predecessor.inbox_level] and the constant [sc_rollup_commitment_frequency].
- Greater-than-or-equal (>=), to ensure inbox_levels are monotonically
increasing. along each branch of commitments. Together with
[assert_commitment_not_too_far_ahead] this is sufficient to limit the
depth of the commitment tree, which is also the number commitments stored
per staker. This constraint must be enforced at submission time.
- Equality (=), so that that L2 blocks are produced at a regular rate. This
ensures that there is only ever one branch of correct commitments,
simplifying refutation logic. This could also be enforced at refutation time
rather than submission time, but doing it here works too.
Because [a >= b && a = b] is equivalent to [a = b], we can the latter as
an optimization.
*)ifRaw_level_repr.(commitment.inbox_level=addpred_levelsc_rollup_commitment_frequency)thenreturnctxtelsefailSc_rollup_bad_inbox_level(** Check invariants on [inbox_level], enforcing overallocation of storage and
regularity of block prorudction.
The constants used by [assert_refine_conditions_met] must be chosen such
that the maximum cost of storage allocated by each staker at most the size
of their deposit.
*)letassert_refine_conditions_metctxtrolluplcccommitment=letopenLwt_tzresult_syntaxinlet*ctxt=assert_commitment_not_too_far_aheadctxtrolluplcccommitmentinassert_commitment_frequencyctxtrollupcommitmentletrefine_stakectxtrollupstakercommitment=letopenLwt_tzresult_syntaxinlet*(lcc,ctxt)=last_cemented_commitmentctxtrollupinlet*(staked_on,ctxt)=find_stakerctxtrollupstakerinlet*ctxt=assert_refine_conditions_metctxtrolluplcccommitmentinletnew_hash=Commitment.hashcommitmentin(* TODO: https://gitlab.com/tezos/tezos/-/issues/2559
Add a test checking that L2 nodes can catch up after going offline. *)letrecgonodectxt=(* WARNING: Do NOT reorder this sequence of ifs.
we must check for staked_on before LCC, since refining
from the LCC to another commit is a valid operation. *)ifCommitment_hash.(node=staked_on)then((* Previously staked commit found:
Insert new commitment if not existing *)let*(ctxt,commitment_size_diff,_was_bound)=Store.Commitments.add(ctxt,rollup)new_hashcommitmentinletlevel=(Raw_context.current_levelctxt).levelinlet*(commitment_added_size_diff,ctxt)=set_commitment_addedctxtrollupnew_hashlevelinlet*(ctxt,staker_count_diff)=Store.Stakers.update(ctxt,rollup)stakernew_hashinlet*(stake_count_size_diff,ctxt)=increase_commitment_stake_countctxtrollupnew_hashinletsize_diff=commitment_size_diff+commitment_added_size_diff+stake_count_size_diff+staker_count_diffin(* First submission adds [sc_rollup_commitment_storage_size_in_bytes] to storage.
Later submission adds 0 due to content-addressing. *)assert(Compare.Int.(size_diff=0||size_diff=sc_rollup_commitment_storage_size_in_bytes));return(new_hash,ctxt)(* See WARNING above. *))elseifCommitment_hash.(node=lcc)then(* We reached the LCC, but [staker] is not staked directly on it.
Thus, we backtracked. Note that everyone is staked indirectly on
the LCC. *)failSc_rollup_staker_backtrackedelselet*(pred,ctxt)=get_predecessorctxtrollupnodeinlet*(_size,ctxt)=increase_commitment_stake_countctxtrollupnodein(go[@ocaml.tailcall])predctxtingoCommitment.(commitment.predecessor)ctxtletpublish_commitmentctxtrollupstakercommitment=letopenLwt_tzresult_syntaxinlet*(ctxt,res)=Store.Stakers.find(ctxt,rollup)stakerinmatchreswith|None->let*ctxt=deposit_stakectxtrollupstakerinrefine_stakectxtrollupstakercommitment|Some_->refine_stakectxtrollupstakercommitmentletcement_commitmentctxtrollupnew_lcc=letopenLwt_tzresult_syntaxinletrefutation_deadline_blocks=Constants_storage.sc_rollup_challenge_window_in_blocksctxtin(* Calling [last_final_commitment] first to trigger failure in case of
non-existing rollup. *)let*(old_lcc,ctxt)=last_cemented_commitmentctxtrollupin(* Get is safe, as [Stakers_size] is initialized on origination. *)let*(ctxt,total_staker_count)=Store.Staker_count.getctxtrollupinifCompare.Int32.(total_staker_count<=0l)thenfailSc_rollup_no_stakerselselet*(new_lcc_commitment,ctxt)=get_commitment_internalctxtrollupnew_lccinlet*(ctxt,new_lcc_added)=Store.Commitment_added.get(ctxt,rollup)new_lccinifCommitment_hash.(new_lcc_commitment.predecessor<>old_lcc)thenfailSc_rollup_parent_not_lccelselet*(new_lcc_stake_count,ctxt)=get_commitment_stake_countctxtrollupnew_lccinifCompare.Int32.(total_staker_count<>new_lcc_stake_count)thenfailSc_rollup_disputedelseifletlevel=(Raw_context.current_levelctxt).levelinRaw_level_repr.(level<addnew_lcc_addedrefutation_deadline_blocks)thenfailSc_rollup_too_recentelse(* update LCC *)let*(ctxt,lcc_size_diff)=Store.Last_cemented_commitment.updatectxtrollupnew_lccinassert(Compare.Int.(lcc_size_diff=0));(* At this point we know all stakers are implicitly staked
on the new LCC, and no one is directly staked on the old LCC. We
can safely deallocate the old LCC.
*)let*ctxt=deallocatectxtrollupold_lccinconsume_n_messagesctxtrollup(Int32.to_int@@Sc_rollup_repr.Number_of_messages.to_int32new_lcc_commitment.number_of_messages)typeconflict_point=Commitment_hash.t*Commitment_hash.t(** [goto_inbox_level ctxt rollup inbox_level commit] Follows the predecessors of [commit] until it
arrives at the exact [inbox_level]. The result is the commit hash at the given inbox level. *)letgoto_inbox_levelctxtrollupinbox_levelcommit=letopenLwt_tzresult_syntaxinletrecgoctxtcommit=let*(info,ctxt)=get_commitment_internalctxtrollupcommitinifRaw_level_repr.(info.Commitment.inbox_level<=inbox_level)then((* Assert that we're exactly at that level. If this isn't the case, we're most likely in a
situation where inbox levels are inconsistent. *)assert(Raw_level_repr.(info.inbox_level=inbox_level));return(commit,ctxt))else(go[@ocaml.tailcall])ctxtinfo.predecessoringoctxtcommitletget_conflict_pointctxtrollupstaker1staker2=letopenLwt_tzresult_syntaxin(* Ensure the LCC is set. *)let*(lcc,ctxt)=last_cemented_commitmentctxtrollupin(* Find out on which commitments the competitors are staked. *)let*(commit1,ctxt)=find_stakerctxtrollupstaker1inlet*(commit2,ctxt)=find_stakerctxtrollupstaker2inlet*()=fail_whenCommitment_hash.((* If PVM is in pre-boot state, there might be stakes on the zero commitment. *)commit1=zero||commit2=zero(* If either commit is the LCC, that also means there can't be a conflict. *)||commit1=lcc||commit2=lcc)Sc_rollup_no_conflictinlet*(commit1_info,ctxt)=get_commitment_internalctxtrollupcommit1inlet*(commit2_info,ctxt)=get_commitment_internalctxtrollupcommit2in(* Make sure that both commits are at the same inbox level. In case they are not move the commit
that is farther ahead to the exact inbox level of the other.
We do this instead of an alternating traversal of either commit to ensure the we can detect
wonky inbox level increases. For example, if the inbox levels decrease in different intervals
between commits for either history, we risk going past the conflict point and accidentally
determined that the commits are not in conflict by joining at the same commit. *)lettarget_inbox_level=Raw_level_repr.mincommit1_info.inbox_levelcommit2_info.inbox_levelinlet*(commit1,ctxt)=goto_inbox_levelctxtrolluptarget_inbox_levelcommit1inlet*(commit2,ctxt)=goto_inbox_levelctxtrolluptarget_inbox_levelcommit2in(* The inbox level of a commitment increases by a fixed amount over the preceding commitment.
We use this fact in the following to efficiently traverse both commitment histories towards
the conflict points. *)letrectraverse_in_parallelctxtcommit1commit2=ifCommitment_hash.(commit1=commit2)then(* This case will most dominantly happen when either commit is part of the other's history.
It occurs when the commit that is farther ahead gets dereferenced to its predecessor often
enough to land at the other commit. *)failSc_rollup_no_conflictelselet*(commit1_info,ctxt)=get_commitment_internalctxtrollupcommit1inlet*(commit2_info,ctxt)=get_commitment_internalctxtrollupcommit2inassert(Raw_level_repr.(commit1_info.inbox_level=commit2_info.inbox_level));ifCommitment_hash.(commit1_info.predecessor=commit2_info.predecessor)then(* Same predecessor means we've found the conflict points. *)return((commit1,commit2),ctxt)else(* Different predecessors means they run in parallel. *)(traverse_in_parallel[@ocaml.tailcall])ctxtcommit1_info.predecessorcommit2_info.predecessorintraverse_in_parallelctxtcommit1commit2letremove_stakerctxtrollupstaker=letopenLwt_tzresult_syntaxinlet*(lcc,ctxt)=last_cemented_commitmentctxtrollupinlet*(ctxt,res)=Store.Stakers.find(ctxt,rollup)stakerinmatchreswith|None->failSc_rollup_not_staked|Somestaked_on->ifCommitment_hash.(staked_on=lcc)thenfailSc_rollup_remove_lccelselet*(ctxt,_size_diff)=Store.Stakers.remove_existing(ctxt,rollup)stakerinlet*ctxt=modify_staker_countctxtrollupInt32.predinletrecgonodectxt=ifCommitment_hash.(node=lcc)thenreturnctxtelselet*(pred,ctxt)=get_predecessorctxtrollupnodeinlet*ctxt=decrease_commitment_stake_countctxtrollupnodein(go[@ocaml.tailcall])predctxtingostaked_onctxtletlistctxt=Storage.Sc_rollup.PVM_kind.keysctxt>|=Result.returnletinitial_levelctxtrollup=letopenLwt_tzresult_syntaxinlet*level=Storage.Sc_rollup.Initial_level.findctxtrollupinmatchlevelwith|None->fail(Sc_rollup_does_not_existrollup)|Somelevel->returnlevel