@@ -461,6 +461,119 @@ type SeedResult struct {
461461 Input * Input
462462}
463463
464+ // AdvanceEpochStatus transitions an epoch through the valid state machine to
465+ // the target status, finding the shortest path through the transition graph.
466+ // The graph mirrors the SQL trigger enforce_epoch_status_transition:
467+ //
468+ // OPEN → CLOSED → INPUTS_PROCESSED → CLAIM_COMPUTED
469+ // CLAIM_COMPUTED → CLAIM_SUBMITTED → CLAIM_ACCEPTED
470+ // CLAIM_COMPUTED → CLAIM_ACCEPTED (PRT, sync catch-up, or reader-only mode)
471+ // CLAIM_SUBMITTED → CLAIM_REJECTED
472+ func AdvanceEpochStatus (
473+ ctx context.Context , t * testing.T ,
474+ repo repository.Repository ,
475+ nameOrAddress string ,
476+ epoch * Epoch ,
477+ target EpochStatus ,
478+ ) {
479+ t .Helper ()
480+
481+ // Adjacency list mirrors the SQL trigger's valid transitions.
482+ next := map [EpochStatus ][]EpochStatus {
483+ EpochStatus_Open : {EpochStatus_Closed },
484+ EpochStatus_Closed : {EpochStatus_InputsProcessed },
485+ EpochStatus_InputsProcessed : {EpochStatus_ClaimComputed },
486+ EpochStatus_ClaimComputed : {EpochStatus_ClaimSubmitted , EpochStatus_ClaimAccepted },
487+ EpochStatus_ClaimSubmitted : {EpochStatus_ClaimAccepted , EpochStatus_ClaimRejected },
488+ }
489+
490+ // BFS to find shortest valid path.
491+ type step struct {
492+ status EpochStatus
493+ path []EpochStatus
494+ }
495+ queue := []step {{status : epoch .Status , path : nil }}
496+ visited := map [EpochStatus ]bool {epoch .Status : true }
497+
498+ var path []EpochStatus
499+ for len (queue ) > 0 {
500+ cur := queue [0 ]
501+ queue = queue [1 :]
502+ if cur .status == target {
503+ path = cur .path
504+ break
505+ }
506+ for _ , n := range next [cur .status ] {
507+ if ! visited [n ] {
508+ visited [n ] = true
509+ p := make ([]EpochStatus , len (cur .path )+ 1 )
510+ copy (p , cur .path )
511+ p [len (cur .path )] = n
512+ queue = append (queue , step {n , p })
513+ }
514+ }
515+ }
516+ if path == nil {
517+ t .Fatalf ("AdvanceEpochStatus: no valid path from %s to %s" ,
518+ epoch .Status , target )
519+ }
520+
521+ for _ , s := range path {
522+ // The DB trigger requires proof fields to be non-null when
523+ // entering CLAIM_COMPUTED. Populate them with dummy values
524+ // so tests that only care about status transitions don't need
525+ // to set up proofs manually.
526+ if s == EpochStatus_ClaimComputed {
527+ setDummyProofFields (ctx , t , repo , nameOrAddress , epoch )
528+ // For PRT apps StoreClaimAndProofs already set the status
529+ // to CLAIM_COMPUTED, so skip the redundant UpdateEpochStatus.
530+ app , err := repo .GetApplication (ctx , nameOrAddress )
531+ require .NoError (t , err )
532+ if app .IsDaveConsensus () {
533+ epoch .Status = s
534+ continue
535+ }
536+ }
537+ epoch .Status = s
538+ err := repo .UpdateEpochStatus (ctx , nameOrAddress , epoch )
539+ require .NoError (t , err )
540+ }
541+ }
542+
543+ // setDummyProofFields populates the proof fields required by the DB trigger
544+ // for the INPUTS_PROCESSED → CLAIM_COMPUTED transition.
545+ // For all apps: machine_hash, outputs_merkle_root, outputs_merkle_proof.
546+ // For PRT apps: additionally commitment and commitment_proof (set via
547+ // StoreClaimAndProofs which also transitions the status atomically).
548+ func setDummyProofFields (
549+ ctx context.Context , t * testing.T ,
550+ repo repository.Repository ,
551+ nameOrAddress string ,
552+ epoch * Epoch ,
553+ ) {
554+ t .Helper ()
555+
556+ proof := & OutputsProof {
557+ OutputsHash : UniqueHash (),
558+ OutputsHashProof : [][32 ]byte {[32 ]byte (UniqueHash ())},
559+ MachineHash : UniqueHash (),
560+ }
561+ err := repo .UpdateEpochOutputsProof (
562+ ctx , epoch .ApplicationID , epoch .Index , proof )
563+ require .NoError (t , err )
564+
565+ app , err := repo .GetApplication (ctx , nameOrAddress )
566+ require .NoError (t , err )
567+ if app .IsDaveConsensus () {
568+ commitHash := UniqueHash ()
569+ epoch .Commitment = & commitHash
570+ epoch .CommitmentProof = []common.Hash {UniqueHash ()}
571+ epoch .Status = EpochStatus_ClaimComputed
572+ err = repo .StoreClaimAndProofs (ctx , epoch , nil )
573+ require .NoError (t , err )
574+ }
575+ }
576+
464577// Seed creates and persists a minimal Application with one Epoch and one Input.
465578func Seed (ctx context.Context , t * testing.T , repo repository.Repository ) * SeedResult {
466579 t .Helper ()
0 commit comments