123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2021 Nomadic Labs <contact@nomadic-labs.com> *)(* Copyright (c) 2022 Trili Tech, <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. *)(* *)(*****************************************************************************)openSc_rollup_reprtypeplayer=Alice|BobmoduleV1=structtypedissection_chunk={state_hash:State_hash.toption;tick:Sc_rollup_tick_repr.t;}letpp_state_hash=letopenFormatinpp_print_option~none:(funppf()->fprintfppf"None")State_hash.ppletpp_dissection_chunkppf{state_hash;tick}=letopenFormatinfprintfppf"State hash:%a@ Tick: %a"pp_state_hashstate_hashSc_rollup_tick_repr.ppticktypegame_state=|Dissectingof{dissection:dissection_chunklist;default_number_of_sections:int;}|Final_moveof{agreed_start_chunk:dissection_chunk;refuted_stop_chunk:dissection_chunk;}typet={turn:player;inbox_snapshot:Sc_rollup_inbox_repr.history_proof;level:Raw_level_repr.t;pvm_name:string;game_state:game_state;}letplayer_encoding=letopenData_encodinginunion~tag_size:`Uint8[case~title:"Alice"(Tag0)(constant"alice")(functionAlice->Some()|_->None)(fun()->Alice);case~title:"Bob"(Tag1)(constant"bob")(functionBob->Some()|_->None)(fun()->Bob);]letplayer_equalp1p2=match(p1,p2)with|Alice,Alice->true|Bob,Bob->true|_,_->falseletdissection_chunk_equal{state_hash;tick}chunk2=Option.equalState_hash.equalstate_hashchunk2.state_hash&&Sc_rollup_tick_repr.equaltickchunk2.tickletgame_state_equalgs1gs2=match(gs1,gs2)with|(Dissecting{dissection=dissection1;default_number_of_sections=default_number_of_sections1;},Dissecting{dissection=dissection2;default_number_of_sections=default_number_of_sections2;})->Compare.Int.equaldefault_number_of_sections1default_number_of_sections2&&List.equaldissection_chunk_equaldissection1dissection2|Dissecting_,_->false|(Final_move{agreed_start_chunk=agreed_start_chunk1;refuted_stop_chunk=refuted_stop_chunk1;},Final_move{agreed_start_chunk=agreed_start_chunk2;refuted_stop_chunk=refuted_stop_chunk2;})->dissection_chunk_equalagreed_start_chunk1agreed_start_chunk2&&dissection_chunk_equalrefuted_stop_chunk1refuted_stop_chunk2|Final_move_,_->falseletequal{turn=turn1;inbox_snapshot=inbox_snapshot1;level=level1;pvm_name=pvm_name1;game_state=game_state1;}{turn=turn2;inbox_snapshot=inbox_snapshot2;level=level2;pvm_name=pvm_name2;game_state=game_state2;}=player_equalturn1turn2&&Sc_rollup_inbox_repr.equal_history_proofinbox_snapshot1inbox_snapshot2&&Raw_level_repr.equallevel1level2&&String.equalpvm_name1pvm_name2&&game_state_equalgame_state1game_state2letstring_of_player=functionAlice->"alice"|Bob->"bob"letpp_playerppfplayer=Format.fprintfppf"%s"(string_of_playerplayer)letopponent=functionAlice->Bob|Bob->Aliceletdissection_chunk_encoding=letopenData_encodinginconv(fun{state_hash;tick}->(state_hash,tick))(fun(state_hash,tick)->{state_hash;tick})(obj2(opt"state"State_hash.encoding)(req"tick"Sc_rollup_tick_repr.encoding))letdissection_encoding=letopenData_encodinginlistdissection_chunk_encodingletgame_state_encoding=letopenData_encodinginunion~tag_size:`Uint8[case~title:"Dissecting"(Tag0)(obj3(req"kind"(constant"Dissecting"))(req"dissection"dissection_encoding)(req"default_number_of_sections"uint8))(function|Dissecting{dissection;default_number_of_sections}->Some((),dissection,default_number_of_sections)|_->None)(fun((),dissection,default_number_of_sections)->Dissecting{dissection;default_number_of_sections});case~title:"Final_move"(Tag1)(obj3(req"kind"(constant"Final_move"))(req"agreed_start_chunk"dissection_chunk_encoding)(req"refuted_stop_chunk"dissection_chunk_encoding))(function|Final_move{agreed_start_chunk;refuted_stop_chunk}->Some((),agreed_start_chunk,refuted_stop_chunk)|_->None)(fun((),agreed_start_chunk,refuted_stop_chunk)->Final_move{agreed_start_chunk;refuted_stop_chunk});]letencoding=letopenData_encodinginconv(fun{turn;inbox_snapshot;level;pvm_name;game_state}->(turn,inbox_snapshot,level,pvm_name,game_state))(fun(turn,inbox_snapshot,level,pvm_name,game_state)->{turn;inbox_snapshot;level;pvm_name;game_state})(obj5(req"turn"player_encoding)(req"inbox_snapshot"Sc_rollup_inbox_repr.history_proof_encoding)(req"level"Raw_level_repr.encoding)(req"pvm_name"string)(req"game_state"game_state_encoding))letpp_dissectionppfd=Format.pp_print_list~pp_sep:(funppf()->Format.pp_print_stringppf";\n")pp_dissection_chunkppfdletpp_game_stateppfgame_state=letopenFormatinmatchgame_statewith|Dissecting{dissection;default_number_of_sections}->fprintfppf"Dissecting %a using %d number of sections"pp_dissectiondissectiondefault_number_of_sections|Final_move{agreed_start_chunk;refuted_stop_chunk}->fprintfppf"Final move to refute %a from %a, opponent failed to refute"pp_dissection_chunkagreed_start_chunkpp_dissection_chunkrefuted_stop_chunkletppppfgame=Format.fprintfppf"%a playing; inbox snapshot = %a; level = %a; pvm_name = %s; game_state \
= %a"pp_playergame.turnSc_rollup_inbox_repr.pp_history_proofgame.inbox_snapshotRaw_level_repr.ppgame.levelgame.pvm_namepp_game_stategame.game_stateendtypeversioned=V1ofV1.tletversioned_encoding=letopenData_encodinginunion[case~title:"V1"(Tag0)V1.encoding(functionV1game->Somegame)(fungame->V1game);]includeV1letof_versioned=functionV1game->game[@@inline]letto_versionedgame=V1game[@@inline]moduleIndex=structtypet={alice:Staker.t;bob:Staker.t}letmakeab=letalice,bob=ifCompare.Int.(Staker.compareab>0)then(b,a)else(a,b)in{alice;bob}letencoding=letopenData_encodinginconv(fun{alice;bob}->(alice,bob))(fun(alice,bob)->makealicebob)(obj2(req"alice"Staker.encoding)(req"bob"Staker.encoding))letcompare{alice=a;bob=b}{alice=c;bob=d}=matchStaker.compareacwith0->Staker.comparebd|x->xletto_path{alice;bob}p=Staker.to_b58checkalice::Staker.to_b58checkbob::pletboth_of_b58check_opt(a,b)=let(let*)=Option.bindinlet*a_staker=Staker.of_b58check_optainlet*b_staker=Staker.of_b58check_optbinSome(makea_stakerb_staker)letof_path=function[a;b]->both_of_b58check_opt(a,b)|_->Noneletpath_length=2letrpc_arg=letdescr="A pair of stakers that index a smart contract rollup refutation game."inletconstruct{alice;bob}=Format.sprintf"%s-%s"(Staker.to_b58checkalice)(Staker.to_b58checkbob)inletdestructs=matchString.split_on_char'-'swith|[a;b]->(matchboth_of_b58check_opt(a,b)with|Somestakers->okstakers|None->Result.error(Format.sprintf"Invalid game index notation %s"s))|_->Result.error(Format.sprintf"Invalid game index notation %s"s)inRPC_arg.make~descr~name:"game_index"~construct~destruct()letstaker{alice;bob}=functionAlice->alice|Bob->bobendletmake_chunkstate_hashtick={state_hash;tick}letinitialinbox~pvm_name~(parent:Sc_rollup_commitment_repr.t)~(child:Sc_rollup_commitment_repr.t)~refuter~defender~default_number_of_sections=let({alice;_}:Index.t)=Index.makerefuterdefenderinletalice_to_play=Staker.equalalicerefuterinletopenSc_rollup_tick_reprinlettick=of_number_of_tickschild.number_of_ticksinletgame_state=Dissecting{dissection=(ifequaltickinitialthen[make_chunk(Somechild.compressed_state)initial;make_chunkNone(nextinitial);]else[make_chunk(Someparent.compressed_state)initial;make_chunk(Somechild.compressed_state)tick;make_chunkNone(nexttick);]);default_number_of_sections;}in{turn=(ifalice_to_playthenAliceelseBob);inbox_snapshot=inbox;level=child.inbox_level;pvm_name;game_state;}typestep=|Dissectionofdissection_chunklist|ProofofSc_rollup_proof_repr.tletstep_encoding=letopenData_encodinginunion~tag_size:`Uint8[case~title:"Dissection"(Tag0)dissection_encoding(functionDissectiond->Somed|_->None)(fund->Dissectiond);case~title:"Proof"(Tag1)Sc_rollup_proof_repr.encoding(functionProofp->Somep|_->None)(funp->Proofp);]letpp_stepppfstep=matchstepwith|Dissectionstates->Format.fprintfppf"Dissection:@ ";Format.pp_print_list~pp_sep:(funppf()->Format.pp_print_stringppf";\n\n")(funppf{state_hash;tick}->Format.fprintfppf"Tick: %a,@ State: %a\n"Sc_rollup_tick_repr.pptick(Format.pp_print_optionState_hash.pp)state_hash)ppfstates|Proofproof->Format.fprintfppf"proof: %a"Sc_rollup_proof_repr.ppprooftyperefutation={choice:Sc_rollup_tick_repr.t;step:step}letpp_refutationppf{choice;step}=Format.fprintfppf"Tick: %a@ Step: %a"Sc_rollup_tick_repr.ppchoicepp_stepstepletrefutation_encoding=letopenData_encodinginconv(fun{choice;step}->(choice,step))(fun(choice,step)->{choice;step})(obj2(req"choice"Sc_rollup_tick_repr.encoding)(req"step"step_encoding))typeinvalid_move=|Dissection_choice_not_foundofSc_rollup_tick_repr.t|Dissection_number_of_sections_mismatchof{expected:Z.t;given:Z.t}|Dissection_invalid_number_of_sectionsofZ.t|Dissection_start_hash_mismatchof{expected:State_hash.toption;given:State_hash.toption;}|Dissection_stop_hash_mismatchofState_hash.toption|Dissection_edge_ticks_mismatchof{dissection_start_tick:Sc_rollup_tick_repr.t;dissection_stop_tick:Sc_rollup_tick_repr.t;chunk_start_tick:Sc_rollup_tick_repr.t;chunk_stop_tick:Sc_rollup_tick_repr.t;}|Dissection_ticks_not_increasing|Dissection_invalid_distribution|Dissection_invalid_successive_states_shape|Proof_unexpected_section_sizeofZ.t|Proof_start_state_hash_mismatchof{start_state_hash:State_hash.toption;start_proof:State_hash.t;}|Proof_stop_state_hash_failed_to_refuteof{stop_state_hash:State_hash.toption;stop_proof:State_hash.toption;}|Proof_stop_state_hash_failed_to_validateof{stop_state_hash:State_hash.toption;stop_proof:State_hash.toption;}|Proof_invalidofstringletpp_invalid_movefmt=letpp_hash_optfmt=function|None->Format.fprintffmt"None"|Somex->State_hash.ppfmtxinfunction|Dissection_choice_not_foundtick->Format.fprintffmt"No section starting with tick %a found"Sc_rollup_tick_repr.pptick|Dissection_number_of_sections_mismatch{expected;given}->Format.fprintffmt"The number of sections must be equal to %a instead of %a"Z.pp_printexpectedZ.pp_printgiven|Dissection_invalid_number_of_sectionsn->Format.fprintffmt"A dissection with %a sections can never be valid"Z.pp_printn|Dissection_start_hash_mismatch{given=None;_}->Format.fprintffmt"The start hash must not be None"|Dissection_start_hash_mismatch{given;expected}->Format.fprintffmt"The start hash should be equal to %a, but the provided hash is %a"pp_hash_optexpectedpp_hash_optgiven|Dissection_stop_hash_mismatchh->Format.fprintffmt"The stop hash should not be equal to %a"pp_hash_opth|Dissection_edge_ticks_mismatch{dissection_start_tick;dissection_stop_tick;chunk_start_tick;chunk_stop_tick;}->Sc_rollup_tick_repr.(Format.fprintffmt"We should have dissection_start_tick(%a) = %a and \
dissection_stop_tick(%a) = %a"ppdissection_start_tickppchunk_start_tickppdissection_stop_tickppchunk_stop_tick)|Dissection_ticks_not_increasing->Format.fprintffmt"Ticks should only increase in dissection"|Dissection_invalid_successive_states_shape->Format.fprintffmt"Cannot return to a Some state after being at a None state"|Dissection_invalid_distribution->Format.fprintffmt"Maximum tick increment in a section cannot be more than half total \
dissection length"|Proof_unexpected_section_sizen->Format.fprintffmt"dist should be equal to 1 in a proof, but got %a"Z.pp_printn|Proof_start_state_hash_mismatch{start_state_hash;start_proof}->Format.fprintffmt"start(%a) should be equal to start_proof(%a)"pp_hash_optstart_state_hashState_hash.ppstart_proof|Proof_stop_state_hash_failed_to_refute{stop_state_hash;stop_proof}->Format.fprintffmt"Trying to refute %a, the stop_proof must not be equal to %a"pp_hash_optstop_state_hashpp_hash_optstop_proof|Proof_stop_state_hash_failed_to_validate{stop_state_hash;stop_proof}->Format.fprintffmt"Trying to validate %a, the stop_proof must be equal to %a"pp_hash_optstop_state_hashpp_hash_optstop_proof|Proof_invalids->Format.fprintffmt"Invalid proof: %s"sletinvalid_move_encoding=letopenData_encodinginunion~tag_size:`Uint8[case~title:"sc_rollup_dissection_choice_not_found"(Tag0)(obj2(req"kind"(constant"dissection_choice_not_found"))(req"tick"Sc_rollup_tick_repr.encoding))(function|Dissection_choice_not_foundtick->Some((),tick)|_->None)(fun((),tick)->Dissection_choice_not_foundtick);case~title:"sc_rollup_dissection_number_of_sections_mismatch"(Tag1)(obj3(req"kind"(constant"dissection_number_of_sections_mismatch"))(req"expected"n)(req"given"n))(function|Dissection_number_of_sections_mismatch{expected;given}->Some((),expected,given)|_->None)(fun((),expected,given)->Dissection_number_of_sections_mismatch{expected;given});case~title:"sc_rollup_dissection_invalid_number_of_sections"(Tag2)(obj2(req"kind"(constant"dissection_invalid_number_of_sections"))(req"value"n))(function|Dissection_invalid_number_of_sectionsvalue->Some((),value)|_->None)(fun((),value)->Dissection_invalid_number_of_sectionsvalue);case~title:"sc_rollup_dissection_unexpected_start_hash"(Tag3)(obj3(req"kind"(constant"dissection_unexpected_start_hash"))(req"expected"(optionState_hash.encoding))(req"given"(optionState_hash.encoding)))(function|Dissection_start_hash_mismatch{expected;given}->Some((),expected,given)|_->None)(fun((),expected,given)->Dissection_start_hash_mismatch{expected;given});case~title:"sc_rollup_dissection_stop_hash_mismatch"(Tag4)(obj2(req"kind"(constant"dissection_stop_hash_mismatch"))(req"hash"(optionState_hash.encoding)))(function|Dissection_stop_hash_mismatchhopt->Some((),hopt)|_->None)(fun((),hopt)->Dissection_stop_hash_mismatchhopt);case~title:"sc_rollup_dissection_edge_ticks_mismatch"(Tag5)(obj5(req"kind"(constant"dissection_edge_ticks_mismatch"))(req"dissection_start_tick"Sc_rollup_tick_repr.encoding)(req"dissection_stop_tick"Sc_rollup_tick_repr.encoding)(req"chunk_start_tick"Sc_rollup_tick_repr.encoding)(req"chunk_stop_tick"Sc_rollup_tick_repr.encoding))(function|Dissection_edge_ticks_mismatche->Some((),e.dissection_start_tick,e.dissection_stop_tick,e.chunk_start_tick,e.chunk_stop_tick)|_->None)(fun((),dissection_start_tick,dissection_stop_tick,chunk_start_tick,chunk_stop_tick)->Dissection_edge_ticks_mismatch{dissection_start_tick;dissection_stop_tick;chunk_start_tick;chunk_stop_tick;});case~title:"sc_rollup_dissection_ticks_not_increasing"(Tag6)(obj1(req"kind"(constant"dissection_ticks_not_increasing")))(functionDissection_ticks_not_increasing->Some()|_->None)(fun()->Dissection_ticks_not_increasing);case~title:"sc_rollup_dissection_invalid_distribution"(Tag7)(obj1(req"kind"(constant"dissection_invalid_distribution")))(functionDissection_invalid_distribution->Some()|_->None)(fun()->Dissection_invalid_distribution);case~title:"sc_rollup_dissection_invalid_successive_states_shape"(Tag8)(obj1(req"kind"(constant"dissection_invalid_successive_states_shape")))(function|Dissection_invalid_successive_states_shape->Some()|_->None)(fun()->Dissection_invalid_successive_states_shape);case~title:"sc_rollup_proof_unexpected_section_size"(Tag9)(obj2(req"kind"(constant"proof_unexpected_section_size"))(req"value"n))(functionProof_unexpected_section_sizen->Some((),n)|_->None)(fun((),n)->Proof_unexpected_section_sizen);case~title:"sc_rollup_proof_start_state_hash_mismatch"(Tag10)(obj3(req"kind"(constant"proof_start_state_hash_mismatch"))(req"start_state_hash"(optionState_hash.encoding))(req"start_proof"State_hash.encoding))(function|Proof_start_state_hash_mismatche->Some((),e.start_state_hash,e.start_proof)|_->None)(fun((),start_state_hash,start_proof)->Proof_start_state_hash_mismatch{start_state_hash;start_proof});case~title:"sc_rollup_proof_stop_state_hash_failed_to_refute"(Tag11)(obj3(req"kind"(constant"proof_stop_state_hash_failed_to_refute"))(req"stop_state_hash"(optionState_hash.encoding))(req"stop_proof"(optionState_hash.encoding)))(function|Proof_stop_state_hash_failed_to_refutee->Some((),e.stop_state_hash,e.stop_proof)|_->None)(fun((),stop_state_hash,stop_proof)->Proof_stop_state_hash_failed_to_refute{stop_state_hash;stop_proof});case~title:"sc_rollup_proof_stop_state_hash_failed_to_validate"(Tag12)(obj3(req"kind"(constant"proof_stop_state_hash_failed_to_validate"))(req"stop_state_hash"(optionState_hash.encoding))(req"stop_proof"(optionState_hash.encoding)))(function|Proof_stop_state_hash_failed_to_validatee->Some((),e.stop_state_hash,e.stop_proof)|_->None)(fun((),stop_state_hash,stop_proof)->Proof_stop_state_hash_failed_to_validate{stop_state_hash;stop_proof});case~title:"sc_rollup_proof_invalid"(Tag13)(obj2(req"kind"(constant"proof_invalid"))(req"message"string))(functionProof_invalids->Some((),s)|_->None)(fun((),s)->Proof_invalids);]typereason=Conflict_resolved|Invalid_moveofinvalid_move|Timeoutletpp_reasonppfreason=matchreasonwith|Conflict_resolved->Format.fprintfppf"conflict resolved"|Invalid_movemv->Format.fprintfppf"invalid move(%a)"pp_invalid_movemv|Timeout->Format.fprintfppf"timeout"letreason_encoding=letopenData_encodinginunion~tag_size:`Uint8[case~title:"Conflict_resolved"(Tag0)(constant"conflict_resolved")(functionConflict_resolved->Some()|_->None)(fun()->Conflict_resolved);case~title:"Invalid_move"(Tag1)invalid_move_encoding(functionInvalid_movereason->Somereason|_->None)(funs->Invalid_moves);case~title:"Timeout"(Tag2)(constant"timeout")(functionTimeout->Some()|_->None)(fun()->Timeout);]typegame_result=Loserof{reason:reason;loser:Staker.t}|Drawletpp_game_resultppfr=letopenFormatinmatchrwith|Loser{reason;loser}->fprintfppf"%a lost because: %a"Staker.pploserpp_reasonreason|Draw->fprintfppf"Draw"letgame_result_encoding=letopenData_encodinginunion~tag_size:`Uint8[case~title:"Loser"(Tag0)(obj3(req"kind"(constant"loser"))(req"reason"reason_encoding)(req"player"Staker.encoding))(function|Loser{reason;loser}->Some((),reason,loser)|_->None)(fun((),reason,loser)->Loser{reason;loser});case~title:"Draw"(Tag1)(obj1(req"kind"(constant"draw")))(functionDraw->Some()|_->None)(fun()->Draw);]typestatus=Ongoing|Endedofgame_resultletpp_statusppfstatus=matchstatuswith|Ongoing->Format.fprintfppf"Game ongoing"|Endedgame_result->Format.fprintfppf"Game ended: %a"pp_game_resultgame_resultletstatus_encoding=letopenData_encodinginunion~tag_size:`Uint8[case~title:"Ongoing"(Tag0)(constant"ongoing")(functionOngoing->Some()|_->None)(fun()->Ongoing);case~title:"Ended"(Tag1)(obj1(req"result"game_result_encoding))(functionEndedr->Somer|_->None)(funr->Endedr);]letinvalid_movereason=letopenLwt_result_syntaxinfail(Invalid_movereason)letfind_choicedissectiontick=letopenLwt_result_syntaxinletrectraversestates=matchstateswith|({state_hash=_;tick=state_tick}ascurr)::next::others->ifSc_rollup_tick_repr.(tick=state_tick)thenreturn(curr,next)elsetraverse(next::others)|_->invalid_move(Dissection_choice_not_foundtick)intraversedissectionletcheckpredreason=letopenLwt_result_syntaxinifpredthenreturn()elseinvalid_movereasonletcheck_dissection~default_number_of_sections~start_chunk~stop_chunkdissection=letopenLwt_result_syntaxinletlen=Z.of_int@@List.lengthdissectioninletdist=Sc_rollup_tick_repr.distancestart_chunk.tickstop_chunk.tickinletshould_be_equal_toexpected=Dissection_number_of_sections_mismatch{expected;given=len}inletnum_sections=Z.of_int@@default_number_of_sectionsinlet*()=ifZ.geqdistnum_sectionsthencheckZ.(equallennum_sections)(should_be_equal_tonum_sections)elseifZ.(gtdistone)thencheckZ.(equallen(succdist))(should_be_equal_toZ.(succdist))elseinvalid_move(Dissection_invalid_number_of_sectionslen)inlet*()=match(List.hddissection,List.last_optdissection)with|Some{state_hash=a;tick=a_tick},Some{state_hash=b;tick=b_tick}->let*()=check(Option.equalState_hash.equalastart_chunk.state_hash&¬(Option.is_nonea))(Dissection_start_hash_mismatch{expected=start_chunk.state_hash;given=a})inlet*()=check(not(Option.equalState_hash.equalbstop_chunk.state_hash))((* If the [b] state is equal to [stop_chunk], that means we
agree on the after state of the section. But, we're trying
to dispute it, it doesn't make sense. *)Dissection_stop_hash_mismatchstop_chunk.state_hash)inSc_rollup_tick_repr.(check(a_tick=start_chunk.tick&&b_tick=stop_chunk.tick)(Dissection_edge_ticks_mismatch{dissection_start_tick=a_tick;dissection_stop_tick=b_tick;chunk_start_tick=start_chunk.tick;chunk_stop_tick=stop_chunk.tick;}))|_->(* This case is probably already handled by the
[Dissection_invalid_number_of_sections] returned above *)invalid_move(Dissection_invalid_number_of_sectionslen)inlethalf_dist=Z.(divdist(of_int2)|>succ)inletrectraversestates=matchstateswith|{state_hash=None;_}::{state_hash=Some_;_}::_->invalid_moveDissection_invalid_successive_states_shape|{tick;_}::({tick=next_tick;state_hash=_}asnext)::others->ifSc_rollup_tick_repr.(tick<next_tick)thenletincr=Sc_rollup_tick_repr.distanceticknext_tickinifZ.(leqincrhalf_dist)thentraverse(next::others)elseinvalid_moveDissection_invalid_distributionelseinvalid_moveDissection_ticks_not_increasing|_->return()intraversedissection(** Check that the chosen interval is a single tick. *)letcheck_proof_distance_is_one~start_tick~stop_tick=letdist=Sc_rollup_tick_repr.distancestart_tickstop_tickincheckZ.(equaldistone)(Proof_unexpected_section_sizedist)(** Check the proof begins with the correct state. *)letcheck_proof_start_state~start_stateproof=letstart_proof=Sc_rollup_proof_repr.startproofincheck(Option.equalState_hash.equalstart_state(Somestart_proof))(Proof_start_state_hash_mismatch{start_state_hash=start_state;start_proof})(** Check the proof stops with a different state than refuted one. *)letcheck_proof_stop_state~stop_stateinput_given(input_request:Sc_rollup_PVM_sig.input_request)proofvalidate=letstop_proof=match(input_given,input_request)with|None,No_input_required|Some_,Initial|Some_,First_after_|Some_,Needs_reveal_->Some(Sc_rollup_proof_repr.stopproof)|Some_,No_input_required|None,Initial|None,First_after_|None,Needs_reveal_->Noneincheck(letb=Option.equalState_hash.equalstop_statestop_proofinifvalidatethenbelsenotb)(ifvalidatethenProof_stop_state_hash_failed_to_validate{stop_state_hash=stop_state;stop_proof}elseProof_stop_state_hash_failed_to_refute{stop_state_hash=stop_state;stop_proof})(** Check the proof validates the stop state. *)letcheck_proof_validate_stop_state~stop_stateinputinput_requestproof=check_proof_stop_state~stop_stateinputinput_requestprooftrue(** Check the proof refutes the stop state. *)letcheck_proof_refute_stop_state~stop_stateinputinput_requestproof=check_proof_stop_state~stop_stateinputinput_requestprooffalseletvalidity_final_move~first_move~proof~game~start_chunk~stop_chunk=letopenLwt_result_syntaxinlet*!res=let{inbox_snapshot;level;pvm_name;_}=gameinlet*!valid=Sc_rollup_proof_repr.validinbox_snapshotlevel~pvm_nameproofinlet*()=iffirst_movethencheck_proof_distance_is_one~start_tick:start_chunk.tick~stop_tick:stop_chunk.tickelsereturn_unitinlet*()=check_proof_start_state~start_state:start_chunk.state_hashproofinmatchvalidwith|Ok(input,input_request)->let*()=iffirst_movethencheck_proof_refute_stop_state~stop_state:stop_chunk.state_hashinputinput_requestproofelsecheck_proof_validate_stop_state~stop_state:stop_chunk.state_hashinputinput_requestproofinreturn_true|_->return_falseinLwt.return@@Result.value~default:falseres(** Returns the validity of the first final move on top of a dissection.
It is valid if and only:
- The distance of the refuted dissection is [1].
- The proof start on the agreed start state.
- The proof stop on the state different than the refuted one.
- The proof is correctly verified.
*)letvalidity_first_final_move~proof~game~start_chunk~stop_chunk=validity_final_move~first_move:true~proof~game~start_chunk~stop_chunk(** Returns the validity of the second final move.
It is valid if and only:
- The proof start on the agreed start state.
- The proof stop on the state validates the refuted one.
- The proof is correctly verified.
*)letvalidity_second_final_move~agreed_start_chunk~refuted_stop_chunk~game~proof=validity_final_move~first_move:false~proof~game~start_chunk:agreed_start_chunk~stop_chunk:refuted_stop_chunkletloser_of_results~alice_result~bob_result=match(alice_result,bob_result)with|true,true->None|false,false->None|false,true->SomeAlice|true,false->SomeBobletplay~stakersgamerefutation=letopenLwt_syntaxinletmk_loserreasonloser=letloser=Index.stakerstakersloserinEither.Left(Loser{loser;reason})inlet*result=letopenLwt_result_syntaxinmatch(refutation.step,game.game_state)with|Dissectionstates,Dissecting{dissection;default_number_of_sections}->let*start_chunk,stop_chunk=find_choicedissectionrefutation.choiceinlet*()=check_dissection~default_number_of_sections~start_chunk~stop_chunkstatesinletnew_game_state=Dissecting{dissection=states;default_number_of_sections}inreturn(Either.Right{turn=opponentgame.turn;inbox_snapshot=game.inbox_snapshot;level=game.level;pvm_name=game.pvm_name;game_state=new_game_state;})|Dissection_,Final_move_->invalid_move(Proof_invalid"Final move has started, unexpected dissection")|Proofproof,Dissecting{dissection;default_number_of_sections=_}->let*start_chunk,stop_chunk=find_choicedissectionrefutation.choiceinlet*!player_result=validity_first_final_move~proof~game~start_chunk~stop_chunkinifplayer_resultthenreturn@@mk_loserConflict_resolved(opponentgame.turn)elseletnew_game_state=letagreed_start_chunk=start_chunkinletrefuted_stop_chunk=stop_chunkinFinal_move{agreed_start_chunk;refuted_stop_chunk}inreturn(Either.Right{turn=opponentgame.turn;inbox_snapshot=game.inbox_snapshot;level=game.level;pvm_name=game.pvm_name;game_state=new_game_state;})|Proofproof,Final_move{agreed_start_chunk;refuted_stop_chunk}->let*!player_result=validity_second_final_move~agreed_start_chunk~refuted_stop_chunk~game~proofinifplayer_resultthen(* If we play when the final move started, the opponent provided
a invalid proof. So if the defender manages to provide a valid
proof, he wins. *)return@@mk_loserConflict_resolved(opponentgame.turn)elsereturn(Either.LeftDraw)inmatchresultwith|Okx->returnx|Errorreason->return@@mk_loserreasongame.turnmoduleInternal_for_tests=structletfind_choice=find_choiceletcheck_dissection=check_dissectionendtypetimeout={alice:int;bob:int;last_turn_level:Raw_level_repr.t}lettimeout_encoding=letopenData_encodinginconv(fun{alice;bob;last_turn_level}->(alice,bob,last_turn_level))(fun(alice,bob,last_turn_level)->{alice;bob;last_turn_level})(obj3(req"alice"int31)(req"bob"int31)(req"last_turn_level"Raw_level_repr.encoding))