88 "encoding/json"
99 "errors"
1010 "fmt"
11+ "math/bits"
1112 "time"
1213
1314 "github.com/cartesi/rollups-node/pkg/emulator"
@@ -28,6 +29,7 @@ type RemoteMachineInterface interface {
2829 Delete ()
2930 ForkServer () (* emulator.RemoteMachine , string , uint32 , error )
3031 ShutdownServer () error
32+ CollectMCycleRootHashes (mcycleEnd , log2McyclePeriod , mcyclePhase uint64 , log2BundleMcycleCount int32 , previousBackTree string ) ([]byte , error )
3133}
3234
3335type proofJson struct {
@@ -230,124 +232,69 @@ func (e *LibCartesiBackend) CmioRxBufferSize() uint64 {
230232 return 1 << emulator .CmioRxBufferLog2Size
231233}
232234
235+ func decodeBreakReason (s string ) BreakReason {
236+ switch s {
237+ case "yielded_automatically" :
238+ return YieldedAutomatically
239+ case "yielded_manually" :
240+ return YieldedManually
241+ case "yielded_softly" :
242+ return YieldedSoftly
243+ case "reached_target_mcycle" :
244+ return ReachedTargetMcycle
245+ case "halted" :
246+ return Halted
247+ case "failed" :
248+ return Failed
249+ default :
250+ return Failed
251+ }
252+ }
253+
233254func (e * LibCartesiBackend ) RunAndCollectRootHashes (
234255 mcycleEnd uint64 ,
235256 state * HashCollectorState ,
236257 timeout time.Duration ,
237258) (reason BreakReason , err error ) {
259+
238260 if state == nil {
239261 return Failed , errors .New ("nil state" )
240262 }
241263 if state .Period == 0 {
242264 return Failed , errors .New ("State.Period must be > 0" )
243265 }
244-
245- // Set up timeout management: calculate absolute deadline if timeout is specified
246- var deadline time.Time
247- hasDeadline := timeout > 0
248- if hasDeadline {
249- deadline = time .Now ().Add (timeout )
250- }
251- remaining := func () time.Duration {
252- if ! hasDeadline {
253- return 0
254- }
255- d := time .Until (deadline )
256- if d <= 0 {
257- return time .Nanosecond
258- }
259- return d
266+ log2Period := uint64 (bits .Len64 (state .Period ) - 1 )
267+ if (uint64 (1 ) << log2Period != state .Period ) {
268+ return Failed , fmt .Errorf ("period must be a power of 2, got %v." , state .Period )
260269 }
261- checkDeadline := func () error {
262- if hasDeadline && time .Now ().After (deadline ) {
263- return errors .New ("runWithRootHashes: deadline exceeded" )
264- }
265- return nil
270+ if err := e .inner .SetTimeout (timeout .Milliseconds ()); err != nil {
271+ return Failed , fmt .Errorf ("failed to set operation timeout: %w" , err )
266272 }
267273
268- if err := checkDeadline (); err != nil {
274+ rawResult , err := e .inner .CollectMCycleRootHashes (mcycleEnd , log2Period , state .Phase , state .BundleLog2 , "" )
275+ if err != nil {
269276 return Failed , err
270277 }
271- cur , err := e .ReadMCycle (remaining ())
278+ result := struct {
279+ RootHashes []string `json:"hashes"`
280+ MCyclePhase uint64 `json:"mcycle_phase"`
281+ BreakReason string `json:"break_reason"`
282+ BackTree json.RawMessage `json:"back_tree,omitempty"`
283+ }{}
284+ err = json .Unmarshal (rawResult , & result )
272285 if err != nil {
273- return Failed , err
286+ return Failed , fmt . Errorf ( "failed to unmarshal CollectMCycleRootHashes result: %w" , err )
274287 }
275288
276- collected := (uint64 )(0 )
277-
278- for {
279- if err := checkDeadline (); err != nil {
280- return Failed , err
281- }
282- if cur >= mcycleEnd {
283- // No more cycles to execute
284- return ReachedTargetMcycle , nil
285- }
286-
287- // Calculate the next collection point: distance to the next multiple of the period
288- // This ensures we collect hashes at regular intervals aligned with the period
289- var step uint64
290- if r := state .Phase % state .Period ; r == 0 {
291- step = state .Period
292- } else {
293- step = state .Period - r
294- }
295-
296- nextHashCycle := cur + step
297- target := min (nextHashCycle , mcycleEnd )
298-
299- // Run the machine until target cycle or until it yields/halts
300- br , err := e .Run (target , remaining ())
301- if err != nil {
302- return Failed , err
303- }
304-
305- // Check where we stopped after the run
306- if err := checkDeadline (); err != nil {
307- return Failed , err
308- }
309- pos , err := e .ReadMCycle (remaining ())
310- if err != nil {
311- return Failed , err
312- }
313-
314- advanced := pos - cur
315- state .Phase = (state .Phase + advanced ) % state .Period
316- cur = pos
317-
318- // Only collect hash if we reached the exact boundary (pos == nextHashCycle)
319- // This ensures "hash after each complete period", matching the C API behavior
320- // and avoiding duplicate collections if the machine stops early due to yields
321- if pos == nextHashCycle {
322- if err := checkDeadline (); err != nil {
323- return Failed , err
324- }
325- h , err := e .GetRootHash (remaining ())
326- if err != nil {
327- return Failed , err
328- }
329-
330- state .Hashes = append (state .Hashes , h )
331-
332- collected ++
333- if state .MaxHashes > 0 && collected >= state .MaxHashes {
334- return YieldedSoftly , nil
335- }
336- }
337-
338- switch br {
339- case ReachedTargetMcycle :
340- if cur >= mcycleEnd {
341- return ReachedTargetMcycle , nil
342- }
343- case YieldedManually :
344- return br , nil
345- case YieldedAutomatically , YieldedSoftly , Halted :
346- return br , nil
347- case Failed :
348- return Failed , errors .New ("run failed" )
349- default :
350- return Failed , errors .New ("unknown break reason" )
289+ // convert from base64 and append to collector state
290+ for i , base64Hash := range result .RootHashes {
291+ hash := Hash {}
292+ if err := decodeB64To32 (& hash , base64Hash ); err != nil {
293+ return Failed , fmt .Errorf ("received an invalid hash during RunAndCollectRootHashes at index %v, with value: %v." , i , base64Hash )
351294 }
295+ state .Hashes = append (state .Hashes , hash )
352296 }
297+ state .Phase = result .MCyclePhase
298+ state .BackTree = result .BackTree
299+ return decodeBreakReason (result .BreakReason ), nil
353300}
0 commit comments