-
Notifications
You must be signed in to change notification settings - Fork 132
core/scheduler: fetch attestation data on sse block event #4095
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
d15b2ed
45051b8
168559f
4981627
136ac08
14add24
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -116,6 +116,7 @@ type Scheduler struct { | |||
| builderEnabled bool | ||||
| schedSlotFunc schedSlotFunc | ||||
| epochResolved map[uint64]chan struct{} // Notification channels for epoch resolution | ||||
| eventTriggeredAttestations sync.Map // Track attestation duties triggered via sse block event (map[uint64]bool) | ||||
DiogoSantoss marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
| } | ||||
|
|
||||
| // SubscribeDuties subscribes a callback function for triggered duties. | ||||
|
|
@@ -185,6 +186,57 @@ func (s *Scheduler) HandleChainReorgEvent(ctx context.Context, epoch eth2p0.Epoc | |||
| } | ||||
| } | ||||
|
|
||||
| // triggerDuty triggers all duty subscribers with the provided duty and definition set. | ||||
| func (s *Scheduler) triggerDuty(ctx context.Context, duty core.Duty, defSet core.DutyDefinitionSet) { | ||||
| instrumentDuty(duty, defSet) | ||||
|
|
||||
| dutyCtx := log.WithCtx(ctx, z.Any("duty", duty)) | ||||
| if duty.Type == core.DutyProposer { | ||||
| var span trace.Span | ||||
| dutyCtx, span = core.StartDutyTrace(dutyCtx, duty, "core/scheduler.scheduleSlot") | ||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure the trace name is right
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't modify this name, it was already like that in charon/core/scheduler/scheduler.go Line 290 in 99bc194
HandleBlockEventAre you suggesting changing to .triggerDuty ?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No suggestion, whatever you think will be useful when browsing trace data in Grafana. |
||||
| defer span.End() | ||||
| } | ||||
|
|
||||
| for _, sub := range s.dutySubs { | ||||
| clone, err := defSet.Clone() | ||||
| if err != nil { | ||||
| log.Error(dutyCtx, "Failed to clone duty definition set", err) | ||||
| return | ||||
| } | ||||
|
|
||||
| if err := sub(dutyCtx, duty, clone); err != nil { | ||||
| log.Error(dutyCtx, "Failed to trigger duty subscriber", err) | ||||
| } | ||||
| } | ||||
| } | ||||
|
|
||||
| // HandleBlockEvent handles block processing events from SSE and triggers early attestation data fetching. | ||||
| func (s *Scheduler) HandleBlockEvent(ctx context.Context, slot eth2p0.Slot) { | ||||
| if !featureset.Enabled(featureset.FetchAttOnBlock) { | ||||
| return | ||||
| } | ||||
|
|
||||
| duty := core.Duty{ | ||||
| Slot: uint64(slot), | ||||
| Type: core.DutyAttester, | ||||
| } | ||||
| defSet, ok := s.getDutyDefinitionSet(duty) | ||||
| if !ok { | ||||
| // Nothing for this duty | ||||
| return | ||||
| } | ||||
|
|
||||
| _, alreadyTriggered := s.eventTriggeredAttestations.LoadOrStore(uint64(slot), true) | ||||
| if alreadyTriggered { | ||||
| return | ||||
| } | ||||
|
|
||||
| log.Debug(ctx, "Early attestation data fetch triggered by SSE block event", z.U64("slot", uint64(slot))) | ||||
|
|
||||
| // Trigger duty immediately (early fetch) | ||||
| go s.triggerDuty(ctx, duty, defSet) | ||||
DiogoSantoss marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
| } | ||||
|
|
||||
| // emitCoreSlot calls all slot subscriptions asynchronously with the provided slot. | ||||
| func (s *Scheduler) emitCoreSlot(ctx context.Context, slot core.Slot) { | ||||
| for _, sub := range s.slotSubs { | ||||
|
|
@@ -276,33 +328,22 @@ func (s *Scheduler) scheduleSlot(ctx context.Context, slot core.Slot) { | |||
| } | ||||
|
|
||||
| // Trigger duty async | ||||
| go func() { | ||||
| if !delaySlotOffset(ctx, slot, duty, s.delayFunc) { | ||||
| return // context cancelled | ||||
| } | ||||
|
|
||||
| instrumentDuty(duty, defSet) | ||||
|
|
||||
| dutyCtx := log.WithCtx(ctx, z.Any("duty", duty)) | ||||
| if duty.Type == core.DutyProposer { | ||||
| var span trace.Span | ||||
|
|
||||
| dutyCtx, span = core.StartDutyTrace(dutyCtx, duty, "core/scheduler.scheduleSlot") | ||||
| defer span.End() | ||||
| } | ||||
|
|
||||
| for _, sub := range s.dutySubs { | ||||
| clone, err := defSet.Clone() // Clone for each subscriber. | ||||
| if err != nil { | ||||
| log.Error(dutyCtx, "Failed to clone duty definition set", err) | ||||
| return | ||||
| go func(duty core.Duty, defSet core.DutyDefinitionSet) { | ||||
| // Special handling for attester duties when FetchAttOnBlock is enabled | ||||
| if duty.Type == core.DutyAttester && featureset.Enabled(featureset.FetchAttOnBlock) { | ||||
| if !s.waitForBlockEventOrTimeout(ctx, slot) { | ||||
| return // context cancelled | ||||
| } | ||||
|
|
||||
| if err := sub(dutyCtx, duty, clone); err != nil { | ||||
| log.Error(dutyCtx, "Failed to trigger duty subscriber", err, z.U64("slot", slot.Slot)) | ||||
| _, alreadyTriggered := s.eventTriggeredAttestations.LoadOrStore(slot.Slot, true) | ||||
| if alreadyTriggered { | ||||
| return // already triggered via block event | ||||
| } | ||||
DiogoSantoss marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
| } else if !delaySlotOffset(ctx, slot, duty, s.delayFunc) { | ||||
| return // context cancelled | ||||
| } | ||||
| }() | ||||
|
|
||||
| s.triggerDuty(ctx, duty, defSet) | ||||
| }(duty, defSet) | ||||
| } | ||||
|
|
||||
| if slot.LastInEpoch() { | ||||
|
|
@@ -333,6 +374,27 @@ func delaySlotOffset(ctx context.Context, slot core.Slot, duty core.Duty, delayF | |||
| } | ||||
| } | ||||
|
|
||||
| // waitForBlockEventOrTimeout waits until the fallback timeout (T=1/3 + 300ms) is reached. | ||||
| // Returns false if the context is cancelled, true otherwise. | ||||
| func (s *Scheduler) waitForBlockEventOrTimeout(ctx context.Context, slot core.Slot) bool { | ||||
| // Calculate fallback timeout: 1/3 + 300ms | ||||
| fn, ok := slotOffsets[core.DutyAttester] | ||||
| if !ok { | ||||
| return true | ||||
| } | ||||
| offset := fn(slot.SlotDuration) + 300*time.Millisecond | ||||
| fallbackDeadline := slot.Time.Add(offset) | ||||
|
|
||||
| select { | ||||
| case <-ctx.Done(): | ||||
| return false | ||||
| case <-s.clock.After(time.Until(fallbackDeadline)): | ||||
| log.Debug(ctx, "Fallback timeout reached for attestation, no block event received, possibly fetching stale head", | ||||
| z.U64("slot", slot.Slot)) | ||||
| return true | ||||
| } | ||||
| } | ||||
|
|
||||
| // resolveDuties resolves the duties for the slot's epoch, caching the results. | ||||
| func (s *Scheduler) resolveDuties(ctx context.Context, slot core.Slot) error { | ||||
| s.setResolvingEpoch(slot.Epoch()) | ||||
|
|
@@ -690,6 +752,30 @@ func (s *Scheduler) trimDuties(epoch uint64) { | |||
| } | ||||
|
|
||||
| delete(s.dutiesByEpoch, epoch) | ||||
|
|
||||
| if featureset.Enabled(featureset.FetchAttOnBlock) { | ||||
| s.trimEventTriggeredAttestations(epoch) | ||||
| } | ||||
| } | ||||
|
|
||||
| // trimEventTriggeredAttestations removes old slot entries from eventTriggeredAttestations. | ||||
| func (s *Scheduler) trimEventTriggeredAttestations(epoch uint64) { | ||||
| _, slotsPerEpoch, err := eth2wrap.FetchSlotsConfig(context.Background(), s.eth2Cl) | ||||
| if err != nil { | ||||
| return | ||||
| } | ||||
|
|
||||
| minSlotToKeep := (epoch + 1) * slotsPerEpoch // first slot of next epoch | ||||
| s.eventTriggeredAttestations.Range(func(key, _ any) bool { | ||||
| slot, ok := key.(uint64) | ||||
| if !ok { | ||||
| return true // continue iteration | ||||
| } | ||||
| if slot < minSlotToKeep { | ||||
| s.eventTriggeredAttestations.Delete(slot) | ||||
| } | ||||
| return true // continue iteration | ||||
| }) | ||||
| } | ||||
|
|
||||
| // submitValidatorRegistrations submits the validator registrations for all DVs. | ||||
|
|
||||
Uh oh!
There was an error while loading. Please reload this page.