Compare commits

...

2 Commits

Author SHA1 Message Date
james-prysm
0d9e4aaaab adding some head tolerance 2026-01-20 11:59:27 -06:00
james-prysm
361cf6f617 adding in optimistic check and timeout 2026-01-20 11:21:46 -06:00
3 changed files with 59 additions and 7 deletions

View File

@@ -0,0 +1,3 @@
### Ignored
- adding a optimistic check for e2e evlauator on synced head, it may be slower post fulu to sync.

View File

@@ -289,12 +289,47 @@ func (r *testRunner) waitForMatchingHead(ctx context.Context, timeout time.Durat
return errors.Wrap(err, "unexpected error requesting head block root from 'ref' beacon node")
}
if bytesutil.ToBytes32(cResp.HeadBlockRoot) == bytesutil.ToBytes32(rResp.HeadBlockRoot) {
return nil
// Head matches, now wait for node to exit optimistic mode.
// For Fulu, the execution client may take additional time to sync and verify payloads.
// Give extra time (up to 2 minutes) for optimistic status to clear.
return r.waitForNonOptimistic(ctx, 2*time.Minute, check)
}
}
}
}
// waitForNonOptimistic waits for a node to exit optimistic mode, with a bounded timeout.
// If the timeout is reached, it logs a warning but does not fail - the evaluator will
// handle optimistic nodes gracefully by skipping finalized/justified checks.
func (r *testRunner) waitForNonOptimistic(ctx context.Context, timeout time.Duration, conn *grpc.ClientConn) error {
start := time.Now()
deadline := start.Add(timeout)
checkClient := eth.NewBeaconChainClient(conn)
for time.Now().Before(deadline) {
select {
case <-ctx.Done():
return ctx.Err()
default:
cResp, err := checkClient.GetChainHead(ctx, &emptypb.Empty{})
if err != nil {
// If we can't get chain head, just continue - head already matched
time.Sleep(100 * time.Millisecond)
continue
}
if !cResp.OptimisticStatus {
log.Infof("Node exited optimistic mode after %s", time.Since(start))
return nil
}
time.Sleep(100 * time.Millisecond)
}
}
// Timeout reached but node is still optimistic - this is OK, evaluator handles it
log.Warnf("Node still in optimistic mode after %s, continuing anyway (evaluator will handle)", timeout)
return nil
}
func (r *testRunner) testCheckpointSync(ctx context.Context, g *errgroup.Group, i int, conns []*grpc.ClientConn, bnAPI, enr, minerEnr string) error {
matchTimeout := 3 * time.Minute
ethNode := eth1.NewNode(i, minerEnr)

View File

@@ -129,11 +129,12 @@ func finishedSyncing(_ *e2etypes.EvaluationContext, conns ...*grpc.ClientConn) e
}
func allNodesHaveSameHead(_ *e2etypes.EvaluationContext, conns ...*grpc.ClientConn) error {
headEpochs := make([]primitives.Epoch, len(conns))
headSlots := make([]primitives.Slot, len(conns))
justifiedRoots := make([][]byte, len(conns))
prevJustifiedRoots := make([][]byte, len(conns))
finalizedRoots := make([][]byte, len(conns))
chainHeads := make([]*eth.ChainHead, len(conns))
optimisticStatus := make([]bool, len(conns))
g, _ := errgroup.WithContext(context.Background())
for i, conn := range conns {
@@ -145,11 +146,12 @@ func allNodesHaveSameHead(_ *e2etypes.EvaluationContext, conns ...*grpc.ClientCo
if err != nil {
return errors.Wrapf(err, "connection number=%d", conIdx)
}
headEpochs[conIdx] = chainHead.HeadEpoch
headSlots[conIdx] = chainHead.HeadSlot
justifiedRoots[conIdx] = chainHead.JustifiedBlockRoot
prevJustifiedRoots[conIdx] = chainHead.PreviousJustifiedBlockRoot
finalizedRoots[conIdx] = chainHead.FinalizedBlockRoot
chainHeads[conIdx] = chainHead
optimisticStatus[conIdx] = chainHead.OptimisticStatus
return nil
})
}
@@ -158,14 +160,26 @@ func allNodesHaveSameHead(_ *e2etypes.EvaluationContext, conns ...*grpc.ClientCo
}
for i := range conns {
if headEpochs[0] != headEpochs[i] {
// Allow head slots to differ by at most 2 slots to account for timing
// differences when querying nodes and chain advancement during evaluation.
slotDiff := headSlots[0] - headSlots[i]
if headSlots[i] > headSlots[0] {
slotDiff = headSlots[i] - headSlots[0]
}
if slotDiff > 2 {
return fmt.Errorf(
"received conflicting head epochs on node %d, expected %d, received %d",
"received conflicting head slots on node %d, expected %d (±2), received %d",
i,
headEpochs[0],
headEpochs[i],
headSlots[0],
headSlots[i],
)
}
// Skip finalized/justified checks for nodes in optimistic mode.
// Optimistic nodes haven't verified execution payloads yet, so their
// finalized/justified state may lag behind fully verified nodes.
if optimisticStatus[i] {
continue
}
if !bytes.Equal(justifiedRoots[0], justifiedRoots[i]) {
return fmt.Errorf(
"received conflicting justified block roots on node %d, expected %#x, received %#x: %s and %s",