Source file refutation_game.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
(** This module implements the refutation game logic of the rollup
node.
When a new L1 block arises, the rollup node asks the L1 node for
the current game it is part of, if any.
If a game is running and it is the rollup operator turn, the rollup
node injects the next move of the winning strategy.
If a game is running and it is not the rollup operator turn, the
rollup node asks the L1 node whether the timeout is reached to play
the timeout argument if possible.
Otherwise, if no game is running, the rollup node asks the L1 node
whether there is a conflict with one of its disputable commitments. If
there is such a conflict with a commitment C', then the rollup node
starts a game to refute C' by starting a game with one of its staker.
*)
open Game
let node_role ~self {alice; bob} =
if Signature.Public_key_hash.equal alice self then Alice
else if Signature.Public_key_hash.equal bob self then Bob
else
assert false
type role = Our_turn of {opponent : Signature.public_key_hash} | Their_turn
let turn ~self game ({alice; bob} as players) =
match (node_role ~self players, game.turn) with
| Alice, Alice -> Our_turn {opponent = bob}
| Bob, Bob -> Our_turn {opponent = alice}
| Alice, Bob -> Their_turn
| Bob, Alice -> Their_turn
(** [inject_next_move node_ctxt ~refutation ~opponent ~commitment
~opponent_commitment] submits an L1 operation to
issue the next move in the refutation game. *)
let inject_next_move node_ctxt ~refutation ~opponent =
let open Lwt_result_syntax in
let refute_operation =
L1_operation.Refute
{
rollup = node_ctxt.Node_context.config.sc_rollup_address;
refutation;
opponent;
}
in
let* _hash =
Injector.check_and_add_pending_operation
node_ctxt.config.mode
refute_operation
in
return_unit
type pvm_intermediate_state =
| Hash of State_hash.t
| Evaluated of Fuel.Accounted.t Pvm_plugin_sig.eval_state
let new_dissection (module Plugin : Protocol_plugin_sig.S) ~opponent
~default_number_of_sections ~commitment_period_tick_offset node_ctxt
last_level ok our_view =
let open Lwt_result_syntax in
let start_hash, start_tick, start_state =
match ok with
| Hash hash, tick -> (hash, tick, None)
| Evaluated ({state_hash; _} as state), tick ->
(state_hash, tick, Some state)
in
let start_chunk = Game.{state_hash = Some start_hash; tick = start_tick} in
let our_state, our_tick = our_view in
let our_state_hash =
Option.map (fun Pvm_plugin_sig.{state_hash; _} -> state_hash) our_state
in
let our_stop_chunk = Game.{state_hash = our_state_hash; tick = our_tick} in
let* dissection =
Plugin.Refutation_game_helpers.make_dissection
(module Plugin)
node_ctxt
~start_state
~start_chunk
~our_stop_chunk
~default_number_of_sections
~commitment_period_tick_offset
~last_level
in
let*! () =
Refutation_game_event.computed_dissection
~opponent
~start_tick
~end_tick:our_tick
dissection
in
return dissection
(** [generate_from_dissection ~default_number_of_sections ~tick_offset node_ctxt
game dissection] traverses the current [dissection] and returns a move which
performs a new dissection of the execution trace or provides a refutation
proof to serve as the next move of the [game]. [tick_offset] is the initial
global tick (since genesis) of the PVM at the start of the commitment
period. *)
let generate_next_dissection (module Plugin : Protocol_plugin_sig.S)
~default_number_of_sections node_ctxt ~opponent
~commitment_period_tick_offset (game : Octez_smart_rollup.Game.t)
(dissection : Octez_smart_rollup.Game.dissection_chunk list) =
let open Lwt_result_syntax in
let rec traverse ok = function
| [] ->
tzfail
Rollup_node_errors.Unreliable_tezos_node_returning_inconsistent_game
| Octez_smart_rollup.Game.{state_hash = their_hash; tick} :: dissection -> (
let start_state =
match ok with
| Hash _, _ -> None
| Evaluated ok_state, _ -> Some ok_state
in
let* our =
Interpreter.state_of_tick
(module Plugin)
node_ctxt
?start_state
~tick:(Z.add tick commitment_period_tick_offset)
game.inbox_level
in
match (their_hash, our) with
| None, None ->
assert false
| Some _, None | None, Some _ -> return (ok, (our, tick))
| Some their_hash, Some ({state_hash = our_hash; _} as our_state) ->
if Octez_smart_rollup.State_hash.equal our_hash their_hash then
traverse (Evaluated our_state, tick) dissection
else return (ok, (our, tick)))
in
match dissection with
| {state_hash = Some hash; tick} :: dissection ->
let* ok, ko = traverse (Hash hash, tick) dissection in
let* dissection =
new_dissection
(module Plugin)
~opponent
~default_number_of_sections
~commitment_period_tick_offset
node_ctxt
game.inbox_level
ok
ko
in
let _, choice = ok in
let _, ko_tick = ko in
let chosen_section_len = Z.abs (Z.sub choice ko_tick) in
return (choice, chosen_section_len, dissection)
| [] | {state_hash = None; _} :: _ ->
tzfail
Rollup_node_errors.Unreliable_tezos_node_returning_inconsistent_game
let next_move (module Plugin : Protocol_plugin_sig.S) node_ctxt ~opponent
~commitment_period_tick_offset (game : Octez_smart_rollup.Game.t) =
let open Lwt_result_syntax in
let final_move start_tick =
let* start_state =
Interpreter.state_of_tick
(module Plugin)
node_ctxt
~tick:(Z.add start_tick commitment_period_tick_offset)
game.inbox_level
in
match start_state with
| None ->
tzfail
Rollup_node_errors.Unreliable_tezos_node_returning_inconsistent_game
| Some {state = start_state; _} ->
let* proof =
Plugin.Refutation_game_helpers.generate_proof
node_ctxt
game
start_state
in
let choice = start_tick in
return (Octez_smart_rollup.Game.Move {choice; step = Proof proof})
in
match game.game_state with
| Dissecting {dissection; default_number_of_sections} ->
let* choice, chosen_section_len, dissection =
generate_next_dissection
(module Plugin)
~default_number_of_sections
node_ctxt
~opponent
~commitment_period_tick_offset
game
dissection
in
if Z.(equal chosen_section_len one) then final_move choice
else
return
(Octez_smart_rollup.Game.Move {choice; step = Dissection dissection})
| Final_move {agreed_start_chunk; refuted_stop_chunk = _} ->
let choice = agreed_start_chunk.tick in
final_move choice
let play_next_move plugin node_ctxt ~commitment_period_tick_offset game opponent
=
let open Lwt_result_syntax in
let* refutation =
next_move plugin node_ctxt ~opponent ~commitment_period_tick_offset game
in
inject_next_move node_ctxt ~refutation ~opponent
let play_timeout (node_ctxt : _ Node_context.t) stakers =
let open Lwt_result_syntax in
let timeout_operation =
L1_operation.Timeout {rollup = node_ctxt.config.sc_rollup_address; stakers}
in
let* _hash =
Injector.check_and_add_pending_operation
node_ctxt.config.mode
timeout_operation
in
return_unit
let play node_ctxt ~self ~commitment_period_tick_offset game opponent =
let open Lwt_result_syntax in
let index = make_index self opponent in
let* plugin = Protocol_plugins.last_proto_plugin node_ctxt in
match turn ~self game index with
| Our_turn {opponent} ->
play_next_move
plugin
node_ctxt
~commitment_period_tick_offset
game
opponent
| Their_turn ->
let module Plugin = (val plugin) in
let* timeout_reached =
Plugin.Refutation_game_helpers.timeout_reached node_ctxt ~self ~opponent
in
when_ timeout_reached @@ fun () ->
let*! () = Refutation_game_event.timeout_detected opponent in
play_timeout node_ctxt index
let play_opening_move node_ctxt (conflict : Octez_smart_rollup.Game.conflict) =
let open Lwt_syntax in
let* () = Refutation_game_event.conflict_detected conflict in
let player_commitment_hash =
Octez_smart_rollup.Commitment.hash conflict.our_commitment
in
let opponent_commitment_hash =
Octez_smart_rollup.Commitment.hash conflict.their_commitment
in
let refutation =
Octez_smart_rollup.Game.Start
{player_commitment_hash; opponent_commitment_hash}
in
inject_next_move node_ctxt ~refutation ~opponent:conflict.other