diff --git a/cmd/main.go b/cmd/main.go index 3606000..55b93c9 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -55,12 +55,14 @@ func main() { // Start the duties checker service in a goroutine dutiesChecker := &services.DutiesChecker{ - Beacon: beacon, - Brain: brain, - Notifier: notifier, - Dappmanager: dappmanager, - PollInterval: 1 * time.Minute, - SlashedNotified: make(map[domain.ValidatorIndex]bool), + Beacon: beacon, + Brain: brain, + Notifier: notifier, + Dappmanager: dappmanager, + PollInterval: 1 * time.Minute, + SlashedNotified: make(map[domain.ValidatorIndex]bool), + PreviouslyAllLive: true, // assume all validators were live at start + PreviouslyOffline: false, } wg.Add(1) go func() { diff --git a/internal/adapters/notifier/notifier.go b/internal/adapters/notifier/notifier.go index 5b74d4d..9188327 100644 --- a/internal/adapters/notifier/notifier.go +++ b/internal/adapters/notifier/notifier.go @@ -121,8 +121,8 @@ func (n *Notifier) SendValidatorLivenessNot(validators []domain.ValidatorIndex, } } if live { - title = fmt.Sprintf("Validator(s) Online: %s", indexesToString(validators)) - body = fmt.Sprintf("✅ Validator(s) %s are back online on %s.", indexesToString(validators), n.Network) + title = fmt.Sprintf("All validators back online (%d)", len(validators)) + body = fmt.Sprintf("✅ All validators are back online on %s (%d).", n.Network, len(validators)) priority = Info status = Resolved isBanner = false @@ -215,7 +215,12 @@ func (n *Notifier) SendBlockProposalNot(validators []domain.ValidatorIndex, epoc // Helper to join validator indexes as comma-separated string func indexesToString(indexes []domain.ValidatorIndex) string { var s []string - for _, idx := range indexes { + max := 10 + for i, idx := range indexes { + if i == max { + s = append(s, "...") + break + } s = append(s, fmt.Sprintf("%d", idx)) } return strings.Join(s, ",") diff --git a/internal/application/services/dutieschecker_service.go b/internal/application/services/dutieschecker_service.go index 13e4c0d..1970a6e 100644 --- a/internal/application/services/dutieschecker_service.go +++ b/internal/application/services/dutieschecker_service.go @@ -17,10 +17,13 @@ type DutiesChecker struct { PollInterval time.Duration lastJustifiedEpoch domain.Epoch - lastLivenessState *bool lastRunHadError bool SlashedNotified map[domain.ValidatorIndex]bool + + // Tracking previous states for notifications + PreviouslyAllLive bool + PreviouslyOffline bool } func (a *DutiesChecker) Run(ctx context.Context) { @@ -85,26 +88,33 @@ func (a *DutiesChecker) performChecks(ctx context.Context, justifiedEpoch domain // Debug print: show offline, online, and allLive status logger.Debug("Liveness check: offline=%v, online=%v, allLive=%v", offline, online, allLive) + logger.Debug("Previously all live: %v, previously offline: %v", a.PreviouslyAllLive, a.PreviouslyOffline) - if len(offline) > 0 && (a.lastLivenessState == nil || *a.lastLivenessState) { + // Check for the first condition: 1 or more validators offline when all were previously live + if len(offline) > 0 && a.PreviouslyAllLive { if notificationsEnabled[domain.ValidatorLiveness] { + logger.Debug("Sending notification for validators going offline: %v", offline) if err := a.Notifier.SendValidatorLivenessNot(offline, false); err != nil { logger.Warn("Error sending validator liveness notification: %v", err) } } - val := false - a.lastLivenessState = &val + a.PreviouslyAllLive = false + a.PreviouslyOffline = true } - if allLive && (a.lastLivenessState == nil || !*a.lastLivenessState) { + + // Check for the second condition: all validators online after 1 or more were offline + if allLive && a.PreviouslyOffline { if notificationsEnabled[domain.ValidatorLiveness] { + logger.Debug("Sending notification for all validators back online: %v", indices) if err := a.Notifier.SendValidatorLivenessNot(indices, true); err != nil { logger.Warn("Error sending validator liveness notification: %v", err) } } - val := true - a.lastLivenessState = &val + a.PreviouslyAllLive = true + a.PreviouslyOffline = false } + // Check block proposals (successful or missed) proposed, missed, err := a.checkProposals(ctx, justifiedEpoch, indices) if err != nil { logger.Error("Error checking block proposals: %v", err) @@ -121,6 +131,7 @@ func (a *DutiesChecker) performChecks(ctx context.Context, justifiedEpoch domain } } + // Check for slashed validators slashed, err := a.Beacon.GetSlashedValidators(ctx, indices) if err != nil { logger.Error("Error fetching slashed validators: %v", err) diff --git a/internal/config/config_loader.go b/internal/config/config_loader.go index 880f7f9..b2b083c 100644 --- a/internal/config/config_loader.go +++ b/internal/config/config_loader.go @@ -29,7 +29,7 @@ func LoadConfig() Config { beaconEndpoint := fmt.Sprintf("http://beacon-chain.%s.dncore.dappnode:3500", network) web3SignerEndpoint := fmt.Sprintf("http://web3signer.%s.dncore.dappnode:9000", network) dappmanagerEndpoint := "http://dappmanager.dappnode" - notifierEndpoint := "http://notifier.dappnode:8080" + notifierEndpoint := "http://notifier.notifications.dappnode:8080" brainEndpoint := fmt.Sprintf("http://brain.web3signer-%s.dappnode", network) // Allow override via environment variables