44mod error;
55
66use alloy:: {
7- primitives:: { Address , B256 } ,
7+ primitives:: { Address , B256 , FixedBytes } ,
88 providers:: { DynProvider , Provider } ,
99} ;
1010use error:: Result ;
@@ -14,37 +14,49 @@ use std::{ops::ControlFlow, sync::Arc, time::Duration};
1414use tokio:: sync:: Mutex ;
1515
1616use cartesi_dave_contracts:: dave_consensus:: DaveConsensus ;
17+ use cartesi_prt_contracts:: safety_gate_task;
1718use cartesi_prt_core:: {
1819 db:: dispute_state_access:: { Input , Leaf } ,
1920 strategy:: player:: Player ,
2021 tournament:: { ArenaSender , allow_revert_rethrow_others} ,
2122} ;
22- use rollups_state_manager:: { Epoch , Proof , StateManager , sync:: Watch } ;
23+ use rollups_state_manager:: { Epoch , Proof , Settlement , StateManager , sync:: Watch } ;
24+
25+ /// `type(ISafetyGateTask).interfaceId`, pinned by
26+ /// `testInterfaceIdMatchesNodeConstant` in `prt/contracts`.
27+ ///
28+ /// Note: Solidity interface ids exclude inherited functions (`result`,
29+ /// `cleanup`, `supportsInterface`), so this cannot be derived by XORing the
30+ /// selectors of the full contract ABI.
31+ const SAFETY_GATE_TASK_INTERFACE_ID : FixedBytes < 4 > = FixedBytes :: new ( [ 0xe1 , 0x97 , 0xd7 , 0xb5 ] ) ;
2332
2433pub struct EpochManager < AS : ArenaSender , SM : StateManager > {
2534 arena_sender : Arc < Mutex < AS > > ,
2635 consensus : Address ,
36+ signer_address : Address ,
2737 sleep_duration : Duration ,
2838 long_block_range_error_codes : Vec < String > ,
2939 state_manager : SM ,
30- last_react_epoch : ( Option < Player < AS > > , u64 ) ,
40+ last_react_epoch : ( Option < Player < AS > > , u64 , Address ) ,
3141}
3242
3343impl < AS : ArenaSender , SM : StateManager > EpochManager < AS , SM > {
3444 pub fn new (
3545 arena_sender : Arc < Mutex < AS > > ,
3646 consensus_address : Address ,
47+ signer_address : Address ,
3748 state_manager : SM ,
3849 sleep_duration : Duration ,
3950 long_block_range_error_codes : Vec < String > ,
4051 ) -> Self {
4152 Self {
4253 arena_sender,
4354 consensus : consensus_address,
55+ signer_address,
4456 sleep_duration,
4557 long_block_range_error_codes,
4658 state_manager,
47- last_react_epoch : ( None , 0 ) ,
59+ last_react_epoch : ( None , 0 , Address :: ZERO ) ,
4860 }
4961 }
5062
@@ -83,9 +95,8 @@ impl<AS: ArenaSender, SM: StateManager> EpochManager<AS, SM> {
8395 ) ? {
8496 Some ( settlement) => {
8597 assert_eq ! (
86- settlement. computation_hash. data( ) ,
87- can_settle. winnerCommitment,
88- "Winner commitment mismatch, notify all users!"
98+ settlement. final_state, can_settle. finalState,
99+ "Winner state mismatch, notify all users!"
89100 ) ;
90101 info ! (
91102 "settle epoch {} with claim {}" ,
@@ -119,12 +130,20 @@ impl<AS: ArenaSender, SM: StateManager> EpochManager<AS, SM> {
119130 . state_manager
120131 . settlement_info ( last_sealed_epoch. epoch_number ) ?
121132 {
122- Some ( _ ) => {
133+ Some ( settlement ) => {
123134 trace ! (
124135 "dispute tournaments for epoch {}" ,
125136 last_sealed_epoch. epoch_number
126137 ) ;
127- self . react_dispute ( provider, & last_sealed_epoch) . await ?
138+ let tournament_address = self
139+ . resolve_tournament_address (
140+ provider. clone ( ) ,
141+ last_sealed_epoch. root_tournament ,
142+ & settlement,
143+ )
144+ . await ?;
145+ self . react_dispute ( provider, & last_sealed_epoch, tournament_address)
146+ . await ?
128147 }
129148 None => {
130149 debug ! (
@@ -141,8 +160,9 @@ impl<AS: ArenaSender, SM: StateManager> EpochManager<AS, SM> {
141160 & mut self ,
142161 provider : DynProvider ,
143162 last_sealed_epoch : & Epoch ,
163+ tournament_address : Address ,
144164 ) -> Result < ( ) > {
145- self . get_latest_player ( last_sealed_epoch, provider) ?;
165+ self . get_latest_player ( last_sealed_epoch, provider, tournament_address ) ?;
146166 self . last_react_epoch
147167 . 0
148168 . as_mut ( )
@@ -157,6 +177,7 @@ impl<AS: ArenaSender, SM: StateManager> EpochManager<AS, SM> {
157177 & mut self ,
158178 last_sealed_epoch : & Epoch ,
159179 provider : DynProvider ,
180+ tournament_address : Address ,
160181 ) -> Result < ( ) > {
161182 let snapshot = self
162183 . state_manager
@@ -167,6 +188,7 @@ impl<AS: ArenaSender, SM: StateManager> EpochManager<AS, SM> {
167188 // we need to instantiate new epoch player with appropriate data
168189 if self . last_react_epoch . 0 . is_none ( )
169190 || self . last_react_epoch . 1 != last_sealed_epoch. epoch_number
191+ || self . last_react_epoch . 2 != tournament_address
170192 {
171193 let inputs = self
172194 . state_manager
@@ -191,21 +213,134 @@ impl<AS: ArenaSender, SM: StateManager> EpochManager<AS, SM> {
191213 leafs,
192214 provider. erased ( ) ,
193215 snapshot. to_string_lossy ( ) . to_string ( ) ,
194- last_sealed_epoch . root_tournament ,
216+ tournament_address ,
195217 last_sealed_epoch. block_created_number ,
196218 self . long_block_range_error_codes . clone ( ) ,
197219 self . state_manager
198220 . epoch_directory ( last_sealed_epoch. epoch_number ) ?,
199221 )
200222 . expect ( "fail to initialize prt player" ) ;
201223
202- self . last_react_epoch = ( Some ( player) , last_sealed_epoch. epoch_number ) ;
224+ self . last_react_epoch = (
225+ Some ( player) ,
226+ last_sealed_epoch. epoch_number ,
227+ tournament_address,
228+ ) ;
229+ }
230+
231+ Ok ( ( ) )
232+ }
233+
234+ /// If the epoch task is a safety gate, participate in the gate (sentry
235+ /// vote and fallback timer) and return the inner task address so the
236+ /// player can interact with the actual tournament.
237+ async fn resolve_tournament_address (
238+ & self ,
239+ provider : DynProvider ,
240+ task_address : Address ,
241+ settlement : & Settlement ,
242+ ) -> Result < Address > {
243+ if let Some ( inner_task) = self
244+ . try_safety_gate ( provider, task_address, settlement)
245+ . await ?
246+ {
247+ Ok ( inner_task)
248+ } else {
249+ Ok ( task_address)
250+ }
251+ }
252+
253+ async fn try_safety_gate (
254+ & self ,
255+ provider : DynProvider ,
256+ task_address : Address ,
257+ settlement : & Settlement ,
258+ ) -> Result < Option < Address > > {
259+ if !supports_interface (
260+ provider. clone ( ) ,
261+ task_address,
262+ SAFETY_GATE_TASK_INTERFACE_ID ,
263+ )
264+ . await
265+ {
266+ return Ok ( None ) ;
267+ }
268+
269+ let safety_gate = safety_gate_task:: SafetyGateTask :: new ( task_address, provider. clone ( ) ) ;
270+
271+ self . try_sentry_vote ( & safety_gate, settlement) . await ?;
272+ try_start_fallback_timer ( & safety_gate) . await ?;
273+
274+ let inner_task = safety_gate. INNER_TASK ( ) . call ( ) . await ?;
275+ Ok ( Some ( inner_task) )
276+ }
277+
278+ async fn try_sentry_vote (
279+ & self ,
280+ safety_gate : & safety_gate_task:: SafetyGateTask :: SafetyGateTaskInstance < DynProvider > ,
281+ settlement : & Settlement ,
282+ ) -> Result < ( ) > {
283+ let is_sentry = safety_gate. isSentry ( self . signer_address ) . call ( ) . await ?;
284+ if !is_sentry {
285+ return Ok ( ( ) ) ;
286+ }
287+
288+ let has_voted = safety_gate. hasVoted ( self . signer_address ) . call ( ) . await ?;
289+ if has_voted {
290+ return Ok ( ( ) ) ;
203291 }
204292
293+ let vote = B256 :: from ( settlement. final_state ) ;
294+ info ! (
295+ "sentry vote {} on safety gate {}" ,
296+ vote,
297+ safety_gate. address( )
298+ ) ;
299+ let tx_result = safety_gate. sentryVote ( vote) . send ( ) . await ;
300+ allow_revert_rethrow_others ( "sentryVote" , tx_result) . await ?;
205301 Ok ( ( ) )
206302 }
207303}
208304
305+ /// The gate never starts its own fallback timer; an offchain actor must do
306+ /// it for liveness whenever sentries are missing or disagree.
307+ async fn try_start_fallback_timer (
308+ safety_gate : & safety_gate_task:: SafetyGateTask :: SafetyGateTaskInstance < DynProvider > ,
309+ ) -> Result < ( ) > {
310+ let can_start = safety_gate. canStartFallbackTimer ( ) . call ( ) . await ?;
311+ if !can_start {
312+ return Ok ( ( ) ) ;
313+ }
314+
315+ info ! (
316+ "start fallback timer on safety gate {}" ,
317+ safety_gate. address( )
318+ ) ;
319+ let tx_result = safety_gate. startFallbackTimer ( ) . send ( ) . await ;
320+ allow_revert_rethrow_others ( "startFallbackTimer" , tx_result) . await ?;
321+ Ok ( ( ) )
322+ }
323+
324+ async fn supports_interface (
325+ provider : DynProvider ,
326+ contract : Address ,
327+ interface_id : FixedBytes < 4 > ,
328+ ) -> bool {
329+ let erc165 = safety_gate_task:: SafetyGateTask :: new ( contract, provider) ;
330+ match erc165. supportsInterface ( interface_id) . call ( ) . await {
331+ Ok ( value) => value,
332+ Err ( err) => {
333+ let message = err. to_string ( ) ;
334+ if message. contains ( "execution reverted" ) {
335+ trace ! ( "supportsInterface reverted: {}" , message) ;
336+ } else {
337+ debug ! ( "supportsInterface call failed: {}" , message) ;
338+ }
339+ false
340+ }
341+ }
342+ }
343+
209344fn to_bytes_32_vec ( proof : Proof ) -> Vec < B256 > {
210345 proof. inner ( ) . iter ( ) . map ( B256 :: from) . collect ( )
211346}
0 commit comments