123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2023 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. *)(* *)(*****************************************************************************)openRefutation_coordinator_typesmodulePlayer=Refutation_playermodulePkh_map=Signature.Public_key_hash.MapmodulePkh_table=Signature.Public_key_hash.Tabletypestate={node_ctxt:Node_context.rw;pending_opponents:unitPkh_table.t}letuntracked_conflictsopponent_playersconflicts=List.filter(funconflict->not@@Pkh_map.memconflict.Game.otheropponent_players)conflicts(* Transform the list of ongoing games [(Game.t * pkh * pkh) list]
into a mapping from opponents' pkhs to their corresponding game
state.
*)letmake_game_mapselfongoing_games=List.fold_left(funacc(game,alice,bob)->letopponent_pkh=ifSignature.Public_key_hash.equalselfalicethenbobelsealiceinPkh_map.addopponent_pkhgameacc)Pkh_map.emptyongoing_gamesleton_processLayer1.{level;_}state=letnode_ctxt=state.node_ctxtinletopenLwt_result_syntaxinletrefute_signer=Node_context.get_operatornode_ctxtOperatinginmatchrefute_signerwith|None->(* Not injecting refutations, don't play refutation games *)return_unit|Someself->letNode_context.{rollup_address;_}=node_ctxtinlet*plugin=Protocol_plugins.last_proto_pluginnode_ctxtinletmodulePlugin=(valplugin)in(* Current conflicts in L1 *)let*conflicts=Plugin.Refutation_game_helpers.get_conflictsstate.node_ctxt.cctxtrollup_addressselfin(* Map of opponents the node is playing against to the corresponding
player worker *)letopponent_players=Pkh_map.of_seq@@List.to_seq@@Player.current_games()in(* Conflicts for which we need to start new refutation players.
Some of these might be ongoing. *)letnew_conflicts=untracked_conflictsopponent_playersconflictsin(* L1 ongoing games *)let*ongoing_games=Plugin.Refutation_game_helpers.get_ongoing_gamesstate.node_ctxt.cctxtrollup_addressselfin(* Map between opponents and their corresponding games *)letongoing_game_map=make_game_mapselfongoing_gamesin(* Launch new players for new conflicts, and play one step *)let*()=List.iter_ep(funconflict->letother=conflict.Octez_smart_rollup.Game.otherinPkh_table.replacestate.pending_opponentsother();letgame=Pkh_map.find_optotherongoing_game_mapinPlayer.init_and_playnode_ctxt~self~conflict~game~level)new_conflictsinlet*!()=(* Play one step of the refutation game in every remaining player *)Pkh_map.iter_p(funopponentworker->matchPkh_map.findopponentongoing_game_mapwith|Somegame->Pkh_table.removestate.pending_opponentsopponent;Player.playworkergame~level|None->(* Kill finished players: those who don't aren't
playing against pending opponents that don't have
ongoing games in the L1 *)ifnot@@Pkh_table.memstate.pending_opponentsopponentthenPlayer.shutdownworkerelseLwt.return_unit)opponent_playersinreturn_unitmoduleTypes=structtypenonrecstate=statetypeparameters=Node_context.rwendmoduleName=struct(* We only have a single coordinator in the node *)typet=unitletencoding=Data_encoding.unitletbase=(* But we can have multiple instances in the unit tests. This is just to
avoid conflicts in the events declarations. *)Refutation_game_event.Coordinator.section@["worker"]letpp__=()letequal()()=trueendmoduleWorker=Worker.MakeSingle(Name)(Request)(Types)typeworker=Worker.infiniteWorker.queueWorker.tmoduleHandlers=structtypeself=workerleton_request:typerrequest_error.worker->(r,request_error)Request.t->(r,request_error)resultLwt.t=funwrequest->letstate=Worker.statewinmatchrequestwith|Request.Processb->protect@@fun()->on_processbstatetypelaunch_error=errortraceleton_launch_w()node_ctxt=Lwt_result.return{node_ctxt;pending_opponents=Pkh_table.create5}leton_error(typeab)_wst(r:(a,b)Request.t)(errs:b):unittzresultLwt.t=letopenLwt_result_syntaxinletrequest_view=Request.viewrinletemit_and_return_errorserrs=let*!()=Refutation_game_event.Coordinator.request_failedrequest_viewsterrsinreturn_unitinmatchrwithRequest.Process_->emit_and_return_errorserrsleton_completion_wr_st=Refutation_game_event.Coordinator.request_completed(Request.viewr)stleton_no_request_=Lwt.return_unitleton_close_w=Lwt.return_unitendlettable=Worker.create_tableQueueletworker_promise,worker_waker=Lwt.task()letstartnode_ctxt=letopenLwt_result_syntaxinlet*!()=Refutation_game_event.Coordinator.starting()inlet+worker=Worker.launchtable()node_ctxt(moduleHandlers)inLwt.wakeupworker_wakerworkerletinitnode_ctxt=letopenLwt_result_syntaxinmatchLwt.stateworker_promisewith|Lwt.Return_->(* Worker already started, nothing to do. *)return_unit|Lwt.Failexn->(* Worker crashed, not recoverable. *)fail[Rollup_node_errors.No_refutation_coordinator;Exnexn]|Lwt.Sleep->(* Never started, start it. *)startnode_ctxt(* This is a refutation coordinator for a single scoru *)letworker=letopenResult_syntaxinlazy(matchLwt.stateworker_promisewith|Lwt.Returnworker->returnworker|Lwt.Fail_|Lwt.Sleep->tzfailRollup_node_errors.No_refutation_coordinator)letprocessb=letopenLwt_result_syntaxinlet*?w=Lazy.forceworkerinlet*!(_pushed:bool)=Worker.Queue.push_requestw(Request.Processb)inreturn_unitletshutdown()=letopenLwt_syntaxinletw=Lazy.forceworkerinmatchwwith|Error_->(* There is no refutation coordinator, nothing to do *)Lwt.return_unit|Okw->(* Shut down all current refutation players *)letgames=Player.current_games()inlet*()=List.iter_s(fun(_opponent,player)->Player.shutdownplayer)gamesinWorker.shutdownw