From 3e8cbf41d88ff0d41953d64124d70acfde1a4a2c Mon Sep 17 00:00:00 2001 From: Sergey <83376337+freak12techno@users.noreply.github.com> Date: Fri, 14 Jun 2024 13:39:28 +0300 Subject: [PATCH] feat: use tendermint validator set for validator.Active (#65) * feat: use tendermint validator set for validator.Active * chore: removed GetActive usage for reporters * chore: removed some .Active() usage * chore: fixed tests * chore: removed .Active from metrics * chore: use snapshot in telegram status command * chore: use snapshot in discord status command * chore: use snapshot in telegram params command * chore: use snapshot in discord params command * chore: removed validators.SetSoftOptOutThreshold and NeedsToSign * chore: final * chore: add tests for State * chore: add tests for Entry --- .golangci.yml | 12 +- pkg/converter/converter.go | 2 - pkg/data/manager.go | 12 -- pkg/metrics/metrics.go | 67 ++++---- pkg/reporters/discord/missing.go | 8 +- pkg/reporters/discord/params.go | 13 +- pkg/reporters/discord/status.go | 36 +++-- pkg/reporters/discord/types.go | 8 +- pkg/reporters/discord/validators.go | 8 +- pkg/reporters/telegram/missing.go | 8 +- pkg/reporters/telegram/params.go | 15 +- pkg/reporters/telegram/status.go | 38 +++-- pkg/reporters/telegram/types.go | 8 +- pkg/reporters/telegram/validators.go | 8 +- pkg/snapshot/manager.go | 2 +- pkg/snapshot/manager_test.go | 6 +- pkg/snapshot/snapshot.go | 33 +--- pkg/snapshot/snapshot_test.go | 199 ++++++++++++----------- pkg/state/manager.go | 29 +++- pkg/state/state.go | 20 +++ pkg/state/state_test.go | 40 +++++ pkg/types/entry.go | 119 ++++++++++++++ pkg/types/entry_test.go | 226 +++++++++++++++++++++++++++ pkg/types/types.go | 77 --------- pkg/types/types_test.go | 91 ----------- pkg/types/validator.go | 7 - templates/discord/Params.md | 1 + templates/telegram/Status.html | 2 +- 28 files changed, 676 insertions(+), 419 deletions(-) create mode 100644 pkg/types/entry.go create mode 100644 pkg/types/entry_test.go diff --git a/.golangci.yml b/.golangci.yml index 0852cfd..1c7f539 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -18,16 +18,11 @@ linters: enable-all: true disable: - funlen - - scopelint - - interfacer - - exhaustivestruct - - maligned - - golint - nlreturn - wrapcheck - gomnd - cyclop - - goerr113 + - err113 - exhaustruct - wsl - lll @@ -46,11 +41,6 @@ linters: - gofmt - ireturn - depguard - - nosnakecase - - ifshort - - structcheck - - deadcode - - varcheck - durationcheck - gocyclo - exhaustive diff --git a/pkg/converter/converter.go b/pkg/converter/converter.go index edd9ce4..78fdd2b 100644 --- a/pkg/converter/converter.go +++ b/pkg/converter/converter.go @@ -79,11 +79,9 @@ func (c *Converter) ValidatorFromCosmosValidator( ConsensusAddressHex: fmt.Sprintf("%x", addr), ConsensusAddressValcons: addr.String(), OperatorAddress: validator.OperatorAddress, - Status: int32(validator.Status), Jailed: validator.Jailed, SigningInfo: valSigningInfo, VotingPower: big.NewFloat(0).SetInt(validator.DelegatorShares.BigInt()), - NeedsToSign: true, } } diff --git a/pkg/data/manager.go b/pkg/data/manager.go index d2cd191..b8c7881 100644 --- a/pkg/data/manager.go +++ b/pkg/data/manager.go @@ -106,8 +106,6 @@ func (manager *Manager) GetValidators(height int64) (types.Validators, []error) validators[index] = validator } - validators.SetVotingPowerPercent() - return validators, nil } @@ -224,16 +222,6 @@ func (manager *Manager) GetValidatorsAndSigningInfoForConsumerChain(height int64 mutex.Unlock() } - threshold, _ := validators.GetSoftOutOutThreshold(manager.config.ConsumerSoftOptOut) - - for _, validator := range validators { - if validator.VotingPower.Cmp(threshold) < 0 { - validator.NeedsToSign = false - } - } - - validators.SetVotingPowerPercent() - return validators, errs } diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index 941c122..241ae1f 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -374,93 +374,92 @@ func (m *Manager) LogNodeReconnect(chain string, node string) { func (m *Manager) LogValidatorStats( chain *configPkg.ChainConfig, - validator *types.Validator, - signatureInfo types.SignatureInto, + entry *types.Entry, ) { m.missingBlocksGauge. With(prometheus.Labels{ "chain": chain.Name, - "moniker": validator.Moniker, - "address": validator.OperatorAddress, + "moniker": entry.Validator.Moniker, + "address": entry.Validator.OperatorAddress, }). - Set(float64(signatureInfo.GetNotSigned())) + Set(float64(entry.SignatureInfo.GetNotSigned())) m.activeBlocksGauge. With(prometheus.Labels{ "chain": chain.Name, - "moniker": validator.Moniker, - "address": validator.OperatorAddress, + "moniker": entry.Validator.Moniker, + "address": entry.Validator.OperatorAddress, }). - Set(float64(signatureInfo.Active)) + Set(float64(entry.SignatureInfo.Active)) m.isActiveGauge. With(prometheus.Labels{ "chain": chain.Name, - "moniker": validator.Moniker, - "address": validator.OperatorAddress, + "moniker": entry.Validator.Moniker, + "address": entry.Validator.OperatorAddress, }). - Set(utils.BoolToFloat64(validator.Active())) + Set(utils.BoolToFloat64(entry.IsActive)) m.isJailedGauge. With(prometheus.Labels{ "chain": chain.Name, - "moniker": validator.Moniker, - "address": validator.OperatorAddress, + "moniker": entry.Validator.Moniker, + "address": entry.Validator.OperatorAddress, }). - Set(utils.BoolToFloat64(validator.Jailed)) + Set(utils.BoolToFloat64(entry.Validator.Jailed)) m.missingBlocksGauge. With(prometheus.Labels{ "chain": chain.Name, - "moniker": validator.Moniker, - "address": validator.OperatorAddress, + "moniker": entry.Validator.Moniker, + "address": entry.Validator.OperatorAddress, }). - Set(float64(signatureInfo.GetNotSigned())) + Set(float64(entry.SignatureInfo.GetNotSigned())) - if validator.SigningInfo != nil { + if entry.Validator.SigningInfo != nil { m.isTombstonedGauge. With(prometheus.Labels{ "chain": chain.Name, - "moniker": validator.Moniker, - "address": validator.OperatorAddress, + "moniker": entry.Validator.Moniker, + "address": entry.Validator.OperatorAddress, }). - Set(utils.BoolToFloat64(validator.SigningInfo.Tombstoned)) + Set(utils.BoolToFloat64(entry.Validator.SigningInfo.Tombstoned)) } - if validator.Active() { + if entry.IsActive { if chain.IsConsumer.Bool { m.needsToSignGauge. With(prometheus.Labels{ "chain": chain.Name, - "moniker": validator.Moniker, - "address": validator.OperatorAddress, + "moniker": entry.Validator.Moniker, + "address": entry.Validator.OperatorAddress, }). - Set(utils.BoolToFloat64(validator.NeedsToSign)) + Set(utils.BoolToFloat64(entry.NeedsToSign)) } m.votingPowerGauge. With(prometheus.Labels{ "chain": chain.Name, - "moniker": validator.Moniker, - "address": validator.OperatorAddress, + "moniker": entry.Validator.Moniker, + "address": entry.Validator.OperatorAddress, }). - Set(validator.VotingPowerPercent) + Set(entry.Validator.VotingPowerPercent) m.cumulativeVotingPowerGauge. With(prometheus.Labels{ "chain": chain.Name, - "moniker": validator.Moniker, - "address": validator.OperatorAddress, + "moniker": entry.Validator.Moniker, + "address": entry.Validator.OperatorAddress, }). - Set(validator.CumulativeVotingPowerPercent) + Set(entry.Validator.CumulativeVotingPowerPercent) m.validatorRankGauge. With(prometheus.Labels{ "chain": chain.Name, - "moniker": validator.Moniker, - "address": validator.OperatorAddress, + "moniker": entry.Validator.Moniker, + "address": entry.Validator.OperatorAddress, }). - Set(float64(validator.Rank)) + Set(float64(entry.Validator.Rank)) } } diff --git a/pkg/reporters/discord/missing.go b/pkg/reporters/discord/missing.go index 429fd41..a6a090f 100644 --- a/pkg/reporters/discord/missing.go +++ b/pkg/reporters/discord/missing.go @@ -3,7 +3,7 @@ package discord import ( "fmt" "main/pkg/constants" - snapshotPkg "main/pkg/snapshot" + "main/pkg/types" "main/pkg/utils" "sort" @@ -28,8 +28,8 @@ func (reporter *Reporter) GetMissingCommand() *Command { } validatorEntries := snapshot.Entries.ToSlice() - activeValidatorsEntries := utils.Filter(validatorEntries, func(v snapshotPkg.Entry) bool { - if !v.Validator.Active() { + activeValidatorsEntries := utils.Filter(validatorEntries, func(v *types.Entry) bool { + if !v.IsActive { return false } @@ -46,7 +46,7 @@ func (reporter *Reporter) GetMissingCommand() *Command { render := missingValidatorsRender{ Config: reporter.Config, - Validators: utils.Map(activeValidatorsEntries, func(v snapshotPkg.Entry) missingValidatorsEntry { + Validators: utils.Map(activeValidatorsEntries, func(v *types.Entry) missingValidatorsEntry { link := reporter.Config.ExplorerConfig.GetValidatorLink(v.Validator) group, _, _ := reporter.Config.MissedBlocksGroups.GetGroup(v.SignatureInfo.GetNotSigned()) link.Text = fmt.Sprintf("%s %s", group.EmojiEnd, v.Validator.Moniker) diff --git a/pkg/reporters/discord/params.go b/pkg/reporters/discord/params.go index abd5214..b7459b1 100644 --- a/pkg/reporters/discord/params.go +++ b/pkg/reporters/discord/params.go @@ -18,10 +18,17 @@ func (reporter *Reporter) GetParamsCommand() *Command { blockTime := reporter.Manager.GetBlockTime() maxTimeToJail := reporter.Manager.GetTimeTillJail(0) - validators := reporter.Manager.GetValidators().ToSlice().GetActive() + snapshot, found := reporter.SnapshotManager.GetNewerSnapshot() + if !found { + reporter.Logger.Info().Msg("No older snapshot on telegram params query!") + reporter.BotRespond(s, i, "Error getting params") + return + } + + activeValidators := snapshot.Entries.GetActive() var amount int if reporter.Config.IsConsumer.Bool { - _, amount = validators.GetSoftOutOutThreshold(reporter.Config.ConsumerSoftOptOut) + _, amount = snapshot.Entries.GetSoftOutOutThreshold(reporter.Config.ConsumerSoftOptOut) } template, err := reporter.TemplatesManager.Render("Params", paramsRender{ @@ -29,7 +36,7 @@ func (reporter *Reporter) GetParamsCommand() *Command { BlockTime: blockTime, MaxTimeToJail: maxTimeToJail, ConsumerOptOutValidators: amount, - Validators: validators, + ValidatorsCount: len(activeValidators), }) if err != nil { reporter.Logger.Error().Err(err).Msg("Error rendering params template") diff --git a/pkg/reporters/discord/status.go b/pkg/reporters/discord/status.go index f47e4b6..174835e 100644 --- a/pkg/reporters/discord/status.go +++ b/pkg/reporters/discord/status.go @@ -36,26 +36,28 @@ func (reporter *Reporter) GetStatusCommand() *Command { return } - entries := make([]statusEntry, len(operatorAddresses)) + snapshot, found := reporter.SnapshotManager.GetNewerSnapshot() + if !found { + reporter.Logger.Info(). + Msg("No older snapshot on discord status query!") + reporter.BotRespond(s, i, "Could not fetch user!") + return + } - for index, operatorAddress := range operatorAddresses { - validator, found := reporter.Manager.GetValidator(operatorAddress) - if !found { - reporter.BotRespond(s, i, fmt.Sprintf( - "Could not find a validator with address %s on %s", - operatorAddress, - reporter.Config.GetName(), - )) - return - } + userEntries := snapshot.Entries.ByValidatorAddresses(operatorAddresses) + + entries := make([]statusEntry, len(userEntries)) + for index, entry := range userEntries { entries[index] = statusEntry{ - Validator: validator, - Link: reporter.Config.ExplorerConfig.GetValidatorLink(validator), + IsActive: entry.IsActive, + NeedsToSign: entry.NeedsToSign, + Validator: entry.Validator, + Link: reporter.Config.ExplorerConfig.GetValidatorLink(entry.Validator), } - if validator.Active() && !validator.Jailed { - signatureInfo, err := reporter.Manager.GetValidatorMissedBlocks(validator) + if entry.IsActive && !entry.Validator.Jailed { + signatureInfo, err := reporter.Manager.GetValidatorMissedBlocks(entry.Validator) entries[index].Error = err entries[index].SigningInfo = signatureInfo } @@ -69,8 +71,8 @@ func (reporter *Reporter) GetStatusCommand() *Command { return utils.BoolToFloat64(second.Validator.Jailed)-utils.BoolToFloat64(first.Validator.Jailed) > 0 } - if first.Validator.Active() != second.Validator.Active() { - return utils.BoolToFloat64(second.Validator.Active())-utils.BoolToFloat64(first.Validator.Active()) > 0 + if first.IsActive != second.IsActive { + return utils.BoolToFloat64(second.IsActive)-utils.BoolToFloat64(first.IsActive) > 0 } return second.Validator.VotingPowerPercent < first.Validator.VotingPowerPercent diff --git a/pkg/reporters/discord/types.go b/pkg/reporters/discord/types.go index 8d06fce..b718f38 100644 --- a/pkg/reporters/discord/types.go +++ b/pkg/reporters/discord/types.go @@ -39,7 +39,7 @@ type paramsRender struct { BlockTime time.Duration MaxTimeToJail time.Duration ConsumerOptOutValidators int - Validators types.Validators + ValidatorsCount int } func (r paramsRender) FormatMinSignedPerWindow() string { @@ -67,7 +67,7 @@ func (r paramsRender) FormatSoftOptOut() string { } func (r paramsRender) GetConsumerRequiredValidators() int { - return len(r.Validators) - r.ConsumerOptOutValidators + return r.ValidatorsCount - r.ConsumerOptOutValidators } func (r paramsRender) FormatSnapshotInterval() string { @@ -89,6 +89,8 @@ type notifierRender struct { } type statusEntry struct { + IsActive bool + NeedsToSign bool Validator *types.Validator Error error SigningInfo types.SignatureInto @@ -108,7 +110,7 @@ func (s statusRender) FormatVotingPower(entry statusEntry) string { text := fmt.Sprintf("%.2f%% VP", entry.Validator.VotingPowerPercent*100) if s.ChainConfig.IsConsumer.Bool { - if entry.Validator.NeedsToSign { + if entry.NeedsToSign { text += ", needs to sign blocks" } else { text += ", does not need to sign blocks" diff --git a/pkg/reporters/discord/validators.go b/pkg/reporters/discord/validators.go index 435be35..b4d1aab 100644 --- a/pkg/reporters/discord/validators.go +++ b/pkg/reporters/discord/validators.go @@ -3,7 +3,7 @@ package discord import ( "fmt" "main/pkg/constants" - snapshotPkg "main/pkg/snapshot" + "main/pkg/types" "main/pkg/utils" "sort" @@ -28,8 +28,8 @@ func (reporter *Reporter) GetValidatorsCommand() *Command { } validatorEntries := snapshot.Entries.ToSlice() - activeValidatorsEntries := utils.Filter(validatorEntries, func(v snapshotPkg.Entry) bool { - return v.Validator.Active() + activeValidatorsEntries := utils.Filter(validatorEntries, func(v *types.Entry) bool { + return v.IsActive }) sort.Slice(activeValidatorsEntries, func(firstIndex, secondIndex int) bool { @@ -41,7 +41,7 @@ func (reporter *Reporter) GetValidatorsCommand() *Command { render := missingValidatorsRender{ Config: reporter.Config, - Validators: utils.Map(activeValidatorsEntries, func(v snapshotPkg.Entry) missingValidatorsEntry { + Validators: utils.Map(activeValidatorsEntries, func(v *types.Entry) missingValidatorsEntry { link := reporter.Config.ExplorerConfig.GetValidatorLink(v.Validator) group, _, _ := reporter.Config.MissedBlocksGroups.GetGroup(v.SignatureInfo.GetNotSigned()) link.Text = fmt.Sprintf("%s %s", group.EmojiEnd, v.Validator.Moniker) diff --git a/pkg/reporters/telegram/missing.go b/pkg/reporters/telegram/missing.go index f502fa6..7392448 100644 --- a/pkg/reporters/telegram/missing.go +++ b/pkg/reporters/telegram/missing.go @@ -3,7 +3,7 @@ package telegram import ( "fmt" "main/pkg/constants" - snapshotPkg "main/pkg/snapshot" + "main/pkg/types" "main/pkg/utils" "sort" @@ -28,8 +28,8 @@ func (reporter *Reporter) HandleMissingValidators(c tele.Context) error { } validatorEntries := snapshot.Entries.ToSlice() - activeValidatorsEntries := utils.Filter(validatorEntries, func(v snapshotPkg.Entry) bool { - if !v.Validator.Active() { + activeValidatorsEntries := utils.Filter(validatorEntries, func(v *types.Entry) bool { + if !v.IsActive { return false } @@ -46,7 +46,7 @@ func (reporter *Reporter) HandleMissingValidators(c tele.Context) error { render := missingValidatorsRender{ Config: reporter.Config, - Validators: utils.Map(activeValidatorsEntries, func(v snapshotPkg.Entry) missingValidatorsEntry { + Validators: utils.Map(activeValidatorsEntries, func(v *types.Entry) missingValidatorsEntry { link := reporter.Config.ExplorerConfig.GetValidatorLink(v.Validator) group, _, _ := reporter.Config.MissedBlocksGroups.GetGroup(v.SignatureInfo.GetNotSigned()) link.Text = fmt.Sprintf("%s %s", group.EmojiEnd, v.Validator.Moniker) diff --git a/pkg/reporters/telegram/params.go b/pkg/reporters/telegram/params.go index 78529d9..56bbc92 100644 --- a/pkg/reporters/telegram/params.go +++ b/pkg/reporters/telegram/params.go @@ -17,10 +17,19 @@ func (reporter *Reporter) HandleParams(c tele.Context) error { blockTime := reporter.Manager.GetBlockTime() maxTimeToJail := reporter.Manager.GetTimeTillJail(0) - validators := reporter.Manager.GetValidators().ToSlice().GetActive() + snapshot, found := reporter.SnapshotManager.GetNewerSnapshot() + if !found { + reporter.Logger.Info(). + Str("sender", c.Sender().Username). + Str("text", c.Text()). + Msg("No older snapshot on telegram params query!") + return reporter.BotReply(c, "Error getting params") + } + + activeValidators := snapshot.Entries.GetActive() var amount int if reporter.Config.IsConsumer.Bool { - _, amount = validators.GetSoftOutOutThreshold(reporter.Config.ConsumerSoftOptOut) + _, amount = snapshot.Entries.GetSoftOutOutThreshold(reporter.Config.ConsumerSoftOptOut) } template, err := reporter.TemplatesManager.Render("Params", paramsRender{ @@ -28,7 +37,7 @@ func (reporter *Reporter) HandleParams(c tele.Context) error { BlockTime: blockTime, MaxTimeToJail: maxTimeToJail, ConsumerOptOutValidators: amount, - Validators: validators, + ValidatorsCount: len(activeValidators), }) if err != nil { return err diff --git a/pkg/reporters/telegram/status.go b/pkg/reporters/telegram/status.go index d1e3b7a..7fc3543 100644 --- a/pkg/reporters/telegram/status.go +++ b/pkg/reporters/telegram/status.go @@ -26,25 +26,29 @@ func (reporter *Reporter) HandleStatus(c tele.Context) error { )) } - entries := make([]statusEntry, len(operatorAddresses)) - - for index, operatorAddress := range operatorAddresses { - validator, found := reporter.Manager.GetValidator(operatorAddress) - if !found { - return reporter.BotReply(c, fmt.Sprintf( - "Could not find a validator with address %s on %s", - operatorAddress, - reporter.Config.GetName(), - )) - } + snapshot, found := reporter.SnapshotManager.GetNewerSnapshot() + if !found { + reporter.Logger.Info(). + Str("sender", c.Sender().Username). + Str("text", c.Text()). + Msg("No older snapshot on telegram status query!") + return reporter.BotReply(c, "Error getting your validators status") + } + + userEntries := snapshot.Entries.ByValidatorAddresses(operatorAddresses) + + entries := make([]statusEntry, len(userEntries)) + for index, entry := range userEntries { entries[index] = statusEntry{ - Validator: validator, - Link: reporter.Config.ExplorerConfig.GetValidatorLink(validator), + Validator: entry.Validator, + Link: reporter.Config.ExplorerConfig.GetValidatorLink(entry.Validator), + IsActive: entry.IsActive, + NeedsToSign: entry.NeedsToSign, } - if validator.Active() && !validator.Jailed { - signatureInfo, err := reporter.Manager.GetValidatorMissedBlocks(validator) + if entry.IsActive && !entry.Validator.Jailed { + signatureInfo, err := reporter.Manager.GetValidatorMissedBlocks(entry.Validator) entries[index].Error = err entries[index].SigningInfo = signatureInfo } @@ -58,8 +62,8 @@ func (reporter *Reporter) HandleStatus(c tele.Context) error { return utils.BoolToFloat64(second.Validator.Jailed)-utils.BoolToFloat64(first.Validator.Jailed) > 0 } - if first.Validator.Active() != second.Validator.Active() { - return utils.BoolToFloat64(second.Validator.Active())-utils.BoolToFloat64(first.Validator.Active()) > 0 + if first.IsActive != second.IsActive { + return utils.BoolToFloat64(second.IsActive)-utils.BoolToFloat64(first.IsActive) > 0 } return second.Validator.VotingPowerPercent < first.Validator.VotingPowerPercent diff --git a/pkg/reporters/telegram/types.go b/pkg/reporters/telegram/types.go index 4d1b5e5..bbcc72c 100644 --- a/pkg/reporters/telegram/types.go +++ b/pkg/reporters/telegram/types.go @@ -42,7 +42,7 @@ type paramsRender struct { BlockTime time.Duration MaxTimeToJail time.Duration ConsumerOptOutValidators int - Validators types.Validators + ValidatorsCount int } func (r paramsRender) FormatMinSignedPerWindow() string { @@ -66,7 +66,7 @@ func (r paramsRender) FormatGroupPercent(group *config.MissedBlocksGroup) string } func (r paramsRender) GetConsumerRequiredValidators() int { - return len(r.Validators) - r.ConsumerOptOutValidators + return r.ValidatorsCount - r.ConsumerOptOutValidators } func (r paramsRender) FormatSnapshotInterval() string { @@ -82,6 +82,8 @@ func (r paramsRender) FormatSoftOptOut() string { } type statusEntry struct { + IsActive bool + NeedsToSign bool Validator *types.Validator Error error SigningInfo types.SignatureInto @@ -101,7 +103,7 @@ func (s statusRender) FormatVotingPower(entry statusEntry) string { text := fmt.Sprintf("%.2f%% VP", entry.Validator.VotingPowerPercent*100) if s.ChainConfig.IsConsumer.Bool { - if entry.Validator.NeedsToSign { + if entry.NeedsToSign { text += ", needs to sign blocks" } else { text += ", does not need to sign blocks" diff --git a/pkg/reporters/telegram/validators.go b/pkg/reporters/telegram/validators.go index c61d5b3..b152ec7 100644 --- a/pkg/reporters/telegram/validators.go +++ b/pkg/reporters/telegram/validators.go @@ -3,7 +3,7 @@ package telegram import ( "fmt" "main/pkg/constants" - snapshotPkg "main/pkg/snapshot" + "main/pkg/types" "main/pkg/utils" "sort" @@ -28,8 +28,8 @@ func (reporter *Reporter) HandleListValidators(c tele.Context) error { } validatorEntries := snapshot.Entries.ToSlice() - activeValidatorsEntries := utils.Filter(validatorEntries, func(v snapshotPkg.Entry) bool { - return v.Validator.Active() + activeValidatorsEntries := utils.Filter(validatorEntries, func(v *types.Entry) bool { + return v.IsActive }) sort.Slice(activeValidatorsEntries, func(firstIndex, secondIndex int) bool { @@ -41,7 +41,7 @@ func (reporter *Reporter) HandleListValidators(c tele.Context) error { render := missingValidatorsRender{ Config: reporter.Config, - Validators: utils.Map(activeValidatorsEntries, func(v snapshotPkg.Entry) missingValidatorsEntry { + Validators: utils.Map(activeValidatorsEntries, func(v *types.Entry) missingValidatorsEntry { link := reporter.Config.ExplorerConfig.GetValidatorLink(v.Validator) group, _, _ := reporter.Config.MissedBlocksGroups.GetGroup(v.SignatureInfo.GetNotSigned()) link.Text = fmt.Sprintf("%s %s", group.EmojiEnd, v.Validator.Moniker) diff --git a/pkg/snapshot/manager.go b/pkg/snapshot/manager.go index b657188..0823dc6 100644 --- a/pkg/snapshot/manager.go +++ b/pkg/snapshot/manager.go @@ -38,7 +38,7 @@ func (m *Manager) CommitNewSnapshot(height int64, snapshot Snapshot) { } for _, entry := range snapshot.Entries { - m.metricsManager.LogValidatorStats(m.config, entry.Validator, entry.SignatureInfo) + m.metricsManager.LogValidatorStats(m.config, entry) } } diff --git a/pkg/snapshot/manager_test.go b/pkg/snapshot/manager_test.go index c1bc376..0112090 100644 --- a/pkg/snapshot/manager_test.go +++ b/pkg/snapshot/manager_test.go @@ -38,12 +38,12 @@ func TestManagerCommitNewSnapshot(t *testing.T) { manager := NewManager(*log, config, metricsManager) manager.CommitNewSnapshot(10, Snapshot{ - Entries: map[string]Entry{ + Entries: types.Entries{ "validator": {Validator: &types.Validator{}}, }, }) manager.CommitNewSnapshot(20, Snapshot{ - Entries: map[string]Entry{ + Entries: types.Entries{ "validator": {Validator: &types.Validator{}}, }, }) @@ -77,7 +77,7 @@ func TestManagerGetNewerSnapshot(t *testing.T) { assert.Nil(t, firstSnapshot, "Snapshot should not be presented!") manager.CommitNewSnapshot(20, Snapshot{ - Entries: map[string]Entry{ + Entries: types.Entries{ "validator": {Validator: &types.Validator{}}, }, }) diff --git a/pkg/snapshot/snapshot.go b/pkg/snapshot/snapshot.go index 965a5f2..5c7540f 100644 --- a/pkg/snapshot/snapshot.go +++ b/pkg/snapshot/snapshot.go @@ -12,27 +12,8 @@ import ( "golang.org/x/exp/slices" ) -type Entry struct { - Validator *types.Validator - SignatureInfo types.SignatureInto -} - -type Entries map[string]Entry - -func (e Entries) ToSlice() []Entry { - entries := make([]Entry, len(e)) - - index := 0 - for _, entry := range e { - entries[index] = entry - index++ - } - - return entries -} - type Snapshot struct { - Entries Entries + Entries types.Entries } func (snapshot *Snapshot) GetReport( @@ -63,7 +44,7 @@ func (snapshot *Snapshot) GetReport( continue } - if entry.Validator.Jailed && !olderEntry.Validator.Jailed && olderEntry.Validator.Active() { + if entry.Validator.Jailed && !olderEntry.Validator.Jailed && olderEntry.IsActive { entries = append(entries, events.ValidatorJailed{ Validator: entry.Validator, }) @@ -75,25 +56,25 @@ func (snapshot *Snapshot) GetReport( }) } - if entry.Validator.Active() && olderEntry.Validator.Active() && entry.Validator.NeedsToSign && !olderEntry.Validator.NeedsToSign { + if entry.IsActive && olderEntry.IsActive && entry.NeedsToSign && !olderEntry.NeedsToSign { entries = append(entries, events.ValidatorJoinedSignatory{ Validator: entry.Validator, }) } - if entry.Validator.Active() && olderEntry.Validator.Active() && !entry.Validator.NeedsToSign && olderEntry.Validator.NeedsToSign { + if entry.IsActive && olderEntry.IsActive && !entry.NeedsToSign && olderEntry.NeedsToSign { entries = append(entries, events.ValidatorLeftSignatory{ Validator: entry.Validator, }) } - if entry.Validator.Active() && !olderEntry.Validator.Active() { + if entry.IsActive && !olderEntry.IsActive { entries = append(entries, events.ValidatorActive{ Validator: entry.Validator, }) } - if !entry.Validator.Active() && olderEntry.Validator.Active() { + if !entry.IsActive && olderEntry.IsActive { entries = append(entries, events.ValidatorInactive{ Validator: entry.Validator, }) @@ -121,7 +102,7 @@ func (snapshot *Snapshot) GetReport( } isTombstoned := hasNewerSigningInfo && entry.Validator.SigningInfo.Tombstoned - if isTombstoned || entry.Validator.Jailed || !entry.Validator.Active() { + if isTombstoned || entry.Validator.Jailed || !entry.IsActive { continue } diff --git a/pkg/snapshot/snapshot_test.go b/pkg/snapshot/snapshot_test.go index 5444177..b19d1c7 100644 --- a/pkg/snapshot/snapshot_test.go +++ b/pkg/snapshot/snapshot_test.go @@ -15,8 +15,8 @@ import ( func TestValidatorCreated(t *testing.T) { t.Parallel() - olderSnapshot := Snapshot{Entries: map[string]Entry{}} - newerSnapshot := Snapshot{Entries: map[string]Entry{ + olderSnapshot := Snapshot{Entries: types.Entries{}} + newerSnapshot := Snapshot{Entries: types.Entries{ "validator": {Validator: &types.Validator{}}, }} @@ -36,15 +36,17 @@ func TestValidatorGroupChanged(t *testing.T) { }, } - olderSnapshot := Snapshot{Entries: map[string]Entry{ + olderSnapshot := Snapshot{Entries: types.Entries{ "validator": { - Validator: &types.Validator{Jailed: false, Status: 3}, + IsActive: true, + Validator: &types.Validator{Jailed: false}, SignatureInfo: types.SignatureInto{NotSigned: 0}, }, }} - newerSnapshot := Snapshot{Entries: map[string]Entry{ + newerSnapshot := Snapshot{Entries: types.Entries{ "validator": { - Validator: &types.Validator{Jailed: false, Status: 3}, + IsActive: true, + Validator: &types.Validator{Jailed: false}, SignatureInfo: types.SignatureInto{NotSigned: 50}, }, }} @@ -67,15 +69,17 @@ func TestValidatorGroupChangedAnomaly(t *testing.T) { }, } - olderSnapshot := Snapshot{Entries: map[string]Entry{ + olderSnapshot := Snapshot{Entries: types.Entries{ "validator": { - Validator: &types.Validator{Jailed: false, Status: 3}, + IsActive: true, + Validator: &types.Validator{Jailed: false}, SignatureInfo: types.SignatureInto{NotSigned: 0}, }, }} - newerSnapshot := Snapshot{Entries: map[string]Entry{ + newerSnapshot := Snapshot{Entries: types.Entries{ "validator": { - Validator: &types.Validator{Jailed: false, Status: 3}, + IsActive: true, + Validator: &types.Validator{Jailed: false}, SignatureInfo: types.SignatureInto{NotSigned: 125}, }, }} @@ -95,13 +99,13 @@ func TestValidatorTombstoned(t *testing.T) { }, } - olderSnapshot := Snapshot{Entries: map[string]Entry{ + olderSnapshot := Snapshot{Entries: types.Entries{ "validator": { Validator: &types.Validator{SigningInfo: &types.SigningInfo{Tombstoned: false}}, SignatureInfo: types.SignatureInto{NotSigned: 0}, }, }} - newerSnapshot := Snapshot{Entries: map[string]Entry{ + newerSnapshot := Snapshot{Entries: types.Entries{ "validator": { Validator: &types.Validator{SigningInfo: &types.SigningInfo{Tombstoned: true}}, SignatureInfo: types.SignatureInto{NotSigned: 0}, @@ -126,15 +130,17 @@ func TestValidatorJailed(t *testing.T) { }, } - olderSnapshot := Snapshot{Entries: map[string]Entry{ + olderSnapshot := Snapshot{Entries: types.Entries{ "validator": { - Validator: &types.Validator{Jailed: false, Status: 3}, + IsActive: true, + Validator: &types.Validator{Jailed: false}, SignatureInfo: types.SignatureInto{NotSigned: 0}, }, }} - newerSnapshot := Snapshot{Entries: map[string]Entry{ + newerSnapshot := Snapshot{Entries: types.Entries{ "validator": { - Validator: &types.Validator{Jailed: true, Status: 1}, + IsActive: false, + Validator: &types.Validator{Jailed: true}, SignatureInfo: types.SignatureInto{NotSigned: 0}, }, }} @@ -164,13 +170,13 @@ func TestValidatorUnjailed(t *testing.T) { }, } - olderSnapshot := Snapshot{Entries: map[string]Entry{ + olderSnapshot := Snapshot{Entries: types.Entries{ "validator": { Validator: &types.Validator{Jailed: true}, SignatureInfo: types.SignatureInto{NotSigned: 0}, }, }} - newerSnapshot := Snapshot{Entries: map[string]Entry{ + newerSnapshot := Snapshot{Entries: types.Entries{ "validator": { Validator: &types.Validator{Jailed: false}, SignatureInfo: types.SignatureInto{NotSigned: 0}, @@ -195,15 +201,19 @@ func TestValidatorJoinedSignatory(t *testing.T) { }, } - olderSnapshot := Snapshot{Entries: map[string]Entry{ + olderSnapshot := Snapshot{Entries: types.Entries{ "validator": { - Validator: &types.Validator{NeedsToSign: false, Status: 3}, + IsActive: true, + NeedsToSign: false, + Validator: &types.Validator{}, SignatureInfo: types.SignatureInto{NotSigned: 0}, }, }} - newerSnapshot := Snapshot{Entries: map[string]Entry{ + newerSnapshot := Snapshot{Entries: types.Entries{ "validator": { - Validator: &types.Validator{NeedsToSign: true, Status: 3}, + IsActive: true, + NeedsToSign: true, + Validator: &types.Validator{}, SignatureInfo: types.SignatureInto{NotSigned: 0}, }, }} @@ -226,15 +236,19 @@ func TestValidatorLeftSignatory(t *testing.T) { }, } - olderSnapshot := Snapshot{Entries: map[string]Entry{ + olderSnapshot := Snapshot{Entries: types.Entries{ "validator": { - Validator: &types.Validator{NeedsToSign: true, Status: 3}, + IsActive: true, + NeedsToSign: true, + Validator: &types.Validator{}, SignatureInfo: types.SignatureInto{NotSigned: 0}, }, }} - newerSnapshot := Snapshot{Entries: map[string]Entry{ + newerSnapshot := Snapshot{Entries: types.Entries{ "validator": { - Validator: &types.Validator{NeedsToSign: false, Status: 3}, + IsActive: true, + NeedsToSign: false, + Validator: &types.Validator{}, SignatureInfo: types.SignatureInto{NotSigned: 0}, }, }} @@ -257,15 +271,17 @@ func TestValidatorInactive(t *testing.T) { }, } - olderSnapshot := Snapshot{Entries: map[string]Entry{ + olderSnapshot := Snapshot{Entries: types.Entries{ "validator": { - Validator: &types.Validator{Jailed: false, Status: 3}, + IsActive: true, + Validator: &types.Validator{Jailed: false}, SignatureInfo: types.SignatureInto{NotSigned: 0}, }, }} - newerSnapshot := Snapshot{Entries: map[string]Entry{ + newerSnapshot := Snapshot{Entries: types.Entries{ "validator": { - Validator: &types.Validator{Jailed: false, Status: 1}, + IsActive: false, + Validator: &types.Validator{Jailed: false}, SignatureInfo: types.SignatureInto{NotSigned: 0}, }, }} @@ -280,10 +296,10 @@ func TestValidatorInactive(t *testing.T) { func TestValidatorChangedKey(t *testing.T) { t.Parallel() - olderSnapshot := Snapshot{Entries: map[string]Entry{ + olderSnapshot := Snapshot{Entries: types.Entries{ "validator": {Validator: &types.Validator{ConsensusAddressValcons: "key1"}}, }} - newerSnapshot := Snapshot{Entries: map[string]Entry{ + newerSnapshot := Snapshot{Entries: types.Entries{ "validator": {Validator: &types.Validator{ConsensusAddressValcons: "key2"}}, }} @@ -296,10 +312,10 @@ func TestValidatorChangedKey(t *testing.T) { func TestValidatorChangedMoniker(t *testing.T) { t.Parallel() - olderSnapshot := Snapshot{Entries: map[string]Entry{ + olderSnapshot := Snapshot{Entries: types.Entries{ "validator": {Validator: &types.Validator{Moniker: "moniker1"}}, }} - newerSnapshot := Snapshot{Entries: map[string]Entry{ + newerSnapshot := Snapshot{Entries: types.Entries{ "validator": {Validator: &types.Validator{Moniker: "moniker2"}}, }} @@ -312,10 +328,10 @@ func TestValidatorChangedMoniker(t *testing.T) { func TestValidatorChangedCommission(t *testing.T) { t.Parallel() - olderSnapshot := Snapshot{Entries: map[string]Entry{ + olderSnapshot := Snapshot{Entries: types.Entries{ "validator": {Validator: &types.Validator{Commission: 0.01}}, }} - newerSnapshot := Snapshot{Entries: map[string]Entry{ + newerSnapshot := Snapshot{Entries: types.Entries{ "validator": {Validator: &types.Validator{Commission: 0.02}}, }} @@ -335,15 +351,17 @@ func TestValidatorActive(t *testing.T) { }, } - olderSnapshot := Snapshot{Entries: map[string]Entry{ + olderSnapshot := Snapshot{Entries: types.Entries{ "validator": { - Validator: &types.Validator{Jailed: false, Status: 1}, + IsActive: false, + Validator: &types.Validator{Jailed: false}, SignatureInfo: types.SignatureInto{NotSigned: 0}, }, }} - newerSnapshot := Snapshot{Entries: map[string]Entry{ + newerSnapshot := Snapshot{Entries: types.Entries{ "validator": { - Validator: &types.Validator{Jailed: false, Status: 3}, + IsActive: true, + Validator: &types.Validator{Jailed: false}, SignatureInfo: types.SignatureInto{NotSigned: 0}, }, }} @@ -365,15 +383,15 @@ func TestValidatorJailedAndChangedGroup(t *testing.T) { }, } - olderSnapshot := Snapshot{Entries: map[string]Entry{ + olderSnapshot := Snapshot{Entries: types.Entries{ "validator": { - Validator: &types.Validator{Jailed: true, Status: 3}, + Validator: &types.Validator{Jailed: true}, SignatureInfo: types.SignatureInto{NotSigned: 0}, }, }} - newerSnapshot := Snapshot{Entries: map[string]Entry{ + newerSnapshot := Snapshot{Entries: types.Entries{ "validator": { - Validator: &types.Validator{Jailed: true, Status: 3}, + Validator: &types.Validator{Jailed: true}, SignatureInfo: types.SignatureInto{NotSigned: 50}, }, }} @@ -393,17 +411,16 @@ func TestTombstonedAndNoPreviousSigningInfo(t *testing.T) { }, } - olderSnapshot := Snapshot{Entries: map[string]Entry{ + olderSnapshot := Snapshot{Entries: types.Entries{ "validator": { - Validator: &types.Validator{Jailed: true, Status: 3}, + Validator: &types.Validator{Jailed: true}, SignatureInfo: types.SignatureInto{NotSigned: 0}, }, }} - newerSnapshot := Snapshot{Entries: map[string]Entry{ + newerSnapshot := Snapshot{Entries: types.Entries{ "validator": { Validator: &types.Validator{ Jailed: true, - Status: 3, SigningInfo: &types.SigningInfo{Tombstoned: true}, }, SignatureInfo: types.SignatureInto{NotSigned: 50}, @@ -415,22 +432,6 @@ func TestTombstonedAndNoPreviousSigningInfo(t *testing.T) { assert.Empty(t, report.Events) } -func TestToSlice(t *testing.T) { - t.Parallel() - - entries := Entries{ - "validator": { - Validator: &types.Validator{Moniker: "test", Jailed: false, Status: 1}, - SignatureInfo: types.SignatureInto{NotSigned: 0}, - }, - } - - slice := entries.ToSlice() - assert.NotEmpty(t, slice) - assert.Len(t, slice, 1) - assert.Equal(t, "test", slice[0].Validator.Moniker) -} - func TestNewMissedBlocksGroupNotPresent(t *testing.T) { t.Parallel() @@ -441,15 +442,17 @@ func TestNewMissedBlocksGroupNotPresent(t *testing.T) { }, } - olderSnapshot := Snapshot{Entries: map[string]Entry{ + olderSnapshot := Snapshot{Entries: types.Entries{ "validator": { - Validator: &types.Validator{Jailed: false, Status: 3}, + IsActive: true, + Validator: &types.Validator{Jailed: false}, SignatureInfo: types.SignatureInto{NotSigned: 0}, }, }} - newerSnapshot := Snapshot{Entries: map[string]Entry{ + newerSnapshot := Snapshot{Entries: types.Entries{ "validator": { - Validator: &types.Validator{Jailed: false, Status: 3}, + IsActive: true, + Validator: &types.Validator{Jailed: false}, SignatureInfo: types.SignatureInto{NotSigned: 150}, }, }} @@ -469,15 +472,17 @@ func TestOldMissedBlocksGroupNotPresent(t *testing.T) { }, } - olderSnapshot := Snapshot{Entries: map[string]Entry{ + olderSnapshot := Snapshot{Entries: types.Entries{ "validator": { - Validator: &types.Validator{Jailed: false, Status: 3}, + IsActive: true, + Validator: &types.Validator{Jailed: false}, SignatureInfo: types.SignatureInto{NotSigned: 150}, }, }} - newerSnapshot := Snapshot{Entries: map[string]Entry{ + newerSnapshot := Snapshot{Entries: types.Entries{ "validator": { - Validator: &types.Validator{Jailed: false, Status: 3}, + IsActive: true, + Validator: &types.Validator{Jailed: false}, SignatureInfo: types.SignatureInto{NotSigned: 0}, }, }} @@ -497,31 +502,37 @@ func TestSorting(t *testing.T) { }, } - olderSnapshot := Snapshot{Entries: map[string]Entry{ + olderSnapshot := Snapshot{Entries: types.Entries{ "validator1": { - Validator: &types.Validator{Jailed: false, Status: 3}, + IsActive: true, + Validator: &types.Validator{Jailed: false}, SignatureInfo: types.SignatureInto{NotSigned: 25}, }, "validator2": { - Validator: &types.Validator{Jailed: false, Status: 3}, + IsActive: true, + Validator: &types.Validator{Jailed: false}, SignatureInfo: types.SignatureInto{NotSigned: 25}, }, "validator3": { - Validator: &types.Validator{Jailed: false, Status: 3, SigningInfo: &types.SigningInfo{Tombstoned: false}}, + IsActive: true, + Validator: &types.Validator{Jailed: false, SigningInfo: &types.SigningInfo{Tombstoned: false}}, SignatureInfo: types.SignatureInto{NotSigned: 25}, }, }} - newerSnapshot := Snapshot{Entries: map[string]Entry{ + newerSnapshot := Snapshot{Entries: types.Entries{ "validator1": { - Validator: &types.Validator{Jailed: true, Status: 3}, + IsActive: true, + Validator: &types.Validator{Jailed: true}, SignatureInfo: types.SignatureInto{NotSigned: 25}, }, "validator2": { - Validator: &types.Validator{Jailed: false, Status: 3}, + IsActive: true, + Validator: &types.Validator{Jailed: false}, SignatureInfo: types.SignatureInto{NotSigned: 75}, }, "validator3": { - Validator: &types.Validator{Jailed: false, Status: 3, SigningInfo: &types.SigningInfo{Tombstoned: true}}, + IsActive: true, + Validator: &types.Validator{Jailed: false, SigningInfo: &types.SigningInfo{Tombstoned: true}}, SignatureInfo: types.SignatureInto{NotSigned: 25}, }, }} @@ -546,43 +557,51 @@ func TestSortingMissedBlocksGroups(t *testing.T) { }, } - olderSnapshot := Snapshot{Entries: map[string]Entry{ + olderSnapshot := Snapshot{Entries: types.Entries{ "validator1": { - Validator: &types.Validator{OperatorAddress: "validator1", Status: 3}, + IsActive: true, + Validator: &types.Validator{OperatorAddress: "validator1"}, SignatureInfo: types.SignatureInto{NotSigned: 25}, }, "validator2": { - Validator: &types.Validator{OperatorAddress: "validator2", Status: 3}, + IsActive: true, + Validator: &types.Validator{OperatorAddress: "validator2"}, SignatureInfo: types.SignatureInto{NotSigned: 75}, }, "validator3": { - Validator: &types.Validator{OperatorAddress: "validator3", Status: 3}, + IsActive: true, + Validator: &types.Validator{OperatorAddress: "validator3"}, SignatureInfo: types.SignatureInto{NotSigned: 125}, }, "validator4": { - Validator: &types.Validator{OperatorAddress: "validator4", Status: 3}, + IsActive: true, + Validator: &types.Validator{OperatorAddress: "validator4"}, SignatureInfo: types.SignatureInto{NotSigned: 75}, }, }} - newerSnapshot := Snapshot{Entries: map[string]Entry{ + newerSnapshot := Snapshot{Entries: types.Entries{ // skipping blocks: 25 -> 75 "validator1": { - Validator: &types.Validator{OperatorAddress: "validator1", Status: 3}, + IsActive: true, + Validator: &types.Validator{OperatorAddress: "validator1"}, SignatureInfo: types.SignatureInto{NotSigned: 75}, }, // skipping blocks: 75 -> 125 "validator2": { - Validator: &types.Validator{OperatorAddress: "validator2", Status: 3}, + IsActive: true, + Validator: &types.Validator{OperatorAddress: "validator2"}, SignatureInfo: types.SignatureInto{NotSigned: 125}, }, // recovering: 125 -> 75 "validator3": { - Validator: &types.Validator{OperatorAddress: "validator3", Status: 3}, + IsActive: true, + Validator: &types.Validator{OperatorAddress: "validator3"}, SignatureInfo: types.SignatureInto{NotSigned: 75}, }, // recovering: 75 -> 25 "validator4": { - Validator: &types.Validator{OperatorAddress: "validator4", Status: 3}, + IsActive: true, + Validator: &types.Validator{OperatorAddress: "validator4"}, SignatureInfo: types.SignatureInto{NotSigned: 25}, }, }} diff --git a/pkg/state/manager.go b/pkg/state/manager.go index 75bd649..9a9a0c8 100644 --- a/pkg/state/manager.go +++ b/pkg/state/manager.go @@ -135,23 +135,44 @@ func (m *Manager) GetMissingBlocksSinceLatest(expected int64) []int64 { } func (m *Manager) GetSnapshot() (snapshotPkg.Snapshot, error) { + lastBlock := m.state.GetLastBlock() + validators := m.state.GetValidators() - entries := make(map[string]snapshotPkg.Entry, len(validators)) + entries := make(types.Entries, len(validators)) neededBlocks := utils.MinInt64(m.config.BlocksWindow, m.GetLastBlockHeight()) for _, validator := range validators { + // Taking the active status from the last block, as there might be a case + // when it's a consumer chain, a validator is an active validator on a provider chain, + // but is not an active validator on a consumer chain (like bottom 5% VP and opted out). + // For sovereign chains: should be the same as taking the validator info from the validators query. + // For consumer chains: might be different when a validator is active on provider + // but is not active on consumer. + _, isActiveAtLastBlock := lastBlock.Validators[validator.ConsensusAddressHex] + signatureInfo, err := m.state.GetValidatorMissedBlocks(validator, neededBlocks) if err != nil { return snapshotPkg.Snapshot{}, err } - entries[validator.OperatorAddress] = snapshotPkg.Entry{ + entries[validator.OperatorAddress] = &types.Entry{ + IsActive: isActiveAtLastBlock, Validator: validator, SignatureInfo: signatureInfo, } } + threshold, _ := entries.GetSoftOutOutThreshold(m.config.ConsumerSoftOptOut) + + for _, entry := range entries { + if entry.Validator.VotingPower.Cmp(threshold) < 0 { + entry.NeedsToSign = false + } + } + + entries.SetVotingPowerPercent() + return snapshotPkg.Snapshot{Entries: entries}, nil } @@ -204,6 +225,10 @@ func (m *Manager) GetValidators() types.ValidatorsMap { return m.state.GetValidators() } +func (m *Manager) GetActiveValidators() types.Validators { + return m.state.GetActiveValidators() +} + func (m *Manager) GetTimeTillJail(missingBlocks int64) time.Duration { return m.state.GetTimeTillJail(m.config, missingBlocks) } diff --git a/pkg/state/state.go b/pkg/state/state.go index 5b83fc7..c812b9f 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -74,6 +74,22 @@ func (s *State) SetBlocks(blocks map[int64]*types.Block) { s.blocks.SetBlocks(blocks) } +func (s *State) GetActiveValidators() types.Validators { + activeValidators := make(types.Validators, 0) + latestBlock := s.GetLastBlock() + if latestBlock == nil { + return activeValidators + } + + for _, validator := range s.validators { + if _, ok := latestBlock.Validators[validator.ConsensusAddressHex]; ok { + activeValidators = append(activeValidators, validator) + } + } + + return activeValidators +} + func (s *State) AddNotifier( operatorAddress string, reporter constants.ReporterName, @@ -125,6 +141,10 @@ func (s *State) GetLastBlockHeight() int64 { return s.blocks.lastHeight } +func (s *State) GetLastBlock() *types.Block { + return s.blocks.blocks[s.blocks.lastHeight] +} + func (s *State) GetValidators() types.ValidatorsMap { return s.validators } diff --git a/pkg/state/state_test.go b/pkg/state/state_test.go index 5ce5bec..2c7324b 100644 --- a/pkg/state/state_test.go +++ b/pkg/state/state_test.go @@ -312,3 +312,43 @@ func TestValidatorsMissedBlocksSomeSkipped(t *testing.T) { assert.Equal(t, int64(1), signature.NotActive, "Argument mismatch!") assert.Equal(t, int64(0), signature.Proposed, "Argument mismatch!") } + +func TestGetLastBlock(t *testing.T) { + t.Parallel() + + state := NewState() + block1 := state.GetLastBlock() + assert.Nil(t, block1) + + state.AddBlock(&types.Block{Height: 1, Signatures: map[string]int32{}, Validators: map[string]bool{}}) + state.AddBlock(&types.Block{Height: 2, Signatures: map[string]int32{}, Validators: map[string]bool{}}) + + block2 := state.GetLastBlock() + assert.Equal(t, int64(2), block2.Height) +} + +func TestGetActiveValidators(t *testing.T) { + t.Parallel() + + state := NewState() + validators1 := state.GetActiveValidators() + assert.Empty(t, validators1) + + state.AddBlock(&types.Block{Height: 1, Signatures: map[string]int32{}, Validators: map[string]bool{ + "validator1": true, + }}) + state.AddBlock(&types.Block{Height: 2, Signatures: map[string]int32{}, Validators: map[string]bool{ + "validator2": true, + }}) + + state.SetValidators(types.ValidatorsMap{ + "validator1": &types.Validator{OperatorAddress: "validator1", ConsensusAddressHex: "validator1"}, + "validator2": &types.Validator{OperatorAddress: "validator2", ConsensusAddressHex: "validator2"}, + }) + + validators2 := state.GetActiveValidators() + assert.Len(t, validators2, 1) + + validator := validators2[0] + assert.Equal(t, "validator2", validator.OperatorAddress) +} diff --git a/pkg/types/entry.go b/pkg/types/entry.go new file mode 100644 index 0000000..a8238cd --- /dev/null +++ b/pkg/types/entry.go @@ -0,0 +1,119 @@ +package types + +import ( + "main/pkg/utils" + "math/big" + "sort" +) + +type Entry struct { + IsActive bool + NeedsToSign bool + Validator *Validator + SignatureInfo SignatureInto + + VotingPowerPercent float64 + CumulativeVotingPowerPercent float64 + Rank int +} + +type Entries map[string]*Entry + +func (e Entries) ToSlice() []*Entry { + entries := make([]*Entry, len(e)) + + index := 0 + for _, entry := range e { + entries[index] = entry + index++ + } + + return entries +} + +func (e Entries) ByValidatorAddresses(addresses []string) []*Entry { + entries := make([]*Entry, 0) + + for _, entry := range e { + if utils.Contains(addresses, entry.Validator.OperatorAddress) { + entries = append(entries, entry) + } + } + + return entries +} + +func (e Entries) GetActive() []*Entry { + activeValidators := make([]*Entry, 0) + for _, entry := range e { + if entry.IsActive { + activeValidators = append(activeValidators, entry) + } + } + + return activeValidators +} + +func (e Entries) GetTotalVotingPower() *big.Float { + sum := big.NewFloat(0) + + for _, entry := range e { + if entry.IsActive { + sum.Add(sum, entry.Validator.VotingPower) + } + } + + return sum +} + +func (e Entries) GetSoftOutOutThreshold(softOptOut float64) (*big.Float, int) { + sortedEntries := e.GetActive() + + if len(sortedEntries) == 0 { + return big.NewFloat(0), 0 + } + + // sorting validators by voting power ascending + sort.Slice(sortedEntries, func(first, second int) bool { + return sortedEntries[first].Validator.VotingPower.Cmp(sortedEntries[second].Validator.VotingPower) < 0 + }) + + totalVP := e.GetTotalVotingPower() + threshold := big.NewFloat(0) + + for index, validator := range sortedEntries { + threshold = big.NewFloat(0).Add(threshold, validator.Validator.VotingPower) + thresholdPercent := big.NewFloat(0).Quo(threshold, totalVP) + + if thresholdPercent.Cmp(big.NewFloat(softOptOut)) > 0 { + return validator.Validator.VotingPower, index + 1 + } + } + + // should've never reached here + return sortedEntries[0].Validator.VotingPower, len(sortedEntries) +} + +func (e Entries) SetVotingPowerPercent() { + totalVP := e.GetTotalVotingPower() + + activeAndSortedEntries := e.GetActive() + + // sorting by voting power desc + sort.Slice(activeAndSortedEntries, func(first, second int) bool { + return activeAndSortedEntries[first].Validator.VotingPower.Cmp(activeAndSortedEntries[second].Validator.VotingPower) > 0 + }) + + var cumulativeVotingPowerPercent float64 = 0 + for index, sortedEntry := range activeAndSortedEntries { + percent, _ := new(big.Float).Quo(sortedEntry.Validator.VotingPower, totalVP).Float64() + + entry := e[sortedEntry.Validator.OperatorAddress] + + entry.VotingPowerPercent = percent + entry.Rank = index + 1 + + cumulativeVotingPowerPercent += percent + entry.CumulativeVotingPowerPercent = cumulativeVotingPowerPercent + } +} diff --git a/pkg/types/entry_test.go b/pkg/types/entry_test.go new file mode 100644 index 0000000..ebd8174 --- /dev/null +++ b/pkg/types/entry_test.go @@ -0,0 +1,226 @@ +package types + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEntriesToSlice(t *testing.T) { + t.Parallel() + + entries := Entries{ + "validator": &Entry{ + Validator: &Validator{Moniker: "test", Jailed: false}, + SignatureInfo: SignatureInto{NotSigned: 0}, + }, + } + + slice := entries.ToSlice() + assert.NotEmpty(t, slice) + assert.Len(t, slice, 1) + assert.Equal(t, "test", slice[0].Validator.Moniker) +} + +func TestEntriesGetActive(t *testing.T) { + t.Parallel() + + entries := Entries{ + "firstaddr": { + IsActive: true, + Validator: &Validator{Moniker: "first", OperatorAddress: "firstaddr"}, + }, + "secondaddr": { + IsActive: false, + Validator: &Validator{Moniker: "second", OperatorAddress: "secondaddr"}, + }, + } + + activeValidators := entries.GetActive() + assert.Len(t, activeValidators, 1) +} + +func TestValidatorsGetTotalVotingPower(t *testing.T) { + t.Parallel() + + entries := Entries{ + "firstaddr": { + IsActive: true, + Validator: &Validator{Moniker: "first", OperatorAddress: "firstaddr", VotingPower: big.NewFloat(1)}, + }, + "secondaddr": { + IsActive: true, + Validator: &Validator{Moniker: "second", OperatorAddress: "secondaddr", VotingPower: big.NewFloat(2)}, + }, + "thirdaddr": { + IsActive: false, + Validator: &Validator{Moniker: "third", OperatorAddress: "thirdaddr", VotingPower: big.NewFloat(3)}, + }, + } + + totalVotingPower := entries.GetTotalVotingPower() + assert.Equal(t, totalVotingPower, big.NewFloat(3)) +} + +func TestEntriesGetSoftOptOutThresholdAchievable(t *testing.T) { + t.Parallel() + + // 3 active validators, with 80%, 15% and 5% vp, with soft-opt-out as 5% + // top 2 should be required to sign and threshold should be 15 + entries := Entries{ + "firstaddr": { + IsActive: true, + Validator: &Validator{ + Moniker: "first", + OperatorAddress: "firstaddr", + VotingPower: big.NewFloat(80), + }, + }, + "secondaddr": { + IsActive: true, + Validator: &Validator{ + Moniker: "second", + OperatorAddress: "secondaddr", + VotingPower: big.NewFloat(15), + }, + }, + "thirdaddr": { + IsActive: false, + Validator: &Validator{ + Moniker: "third", + OperatorAddress: "thirdaddr", + VotingPower: big.NewFloat(2), + }, + }, + "fourthaddr": { + IsActive: true, + Validator: &Validator{ + Moniker: "fourth", + OperatorAddress: "fourthaddr", + VotingPower: big.NewFloat(5), + }, + }, + } + + threshold, count := entries.GetSoftOutOutThreshold(0.05) + assert.Equal(t, big.NewFloat(15), threshold) + assert.Equal(t, 2, count) +} + +func TestEntriesGetSoftOptOutThresholdNotAchievable(t *testing.T) { + t.Parallel() + + // 3 active validators, with 80%, 15% and 5% vp, with not achievable threshold (like -0.05) + // it should require all active validators to be signing + entries := Entries{ + "firstaddr": { + IsActive: true, + Validator: &Validator{ + Moniker: "first", + OperatorAddress: "firstaddr", + VotingPower: big.NewFloat(80), + }, + }, + "secondaddr": { + IsActive: true, + Validator: &Validator{ + Moniker: "second", + OperatorAddress: "secondaddr", + VotingPower: big.NewFloat(15), + }, + }, + "thirdaddr": { + IsActive: false, + Validator: &Validator{ + Moniker: "third", + OperatorAddress: "thirdaddr", + VotingPower: big.NewFloat(2), + }, + }, + "fourthaddr": { + IsActive: true, + Validator: &Validator{ + Moniker: "fourth", + OperatorAddress: "fourthaddr", + VotingPower: big.NewFloat(5), + }, + }, + } + + threshold, count := entries.GetSoftOutOutThreshold(1) + assert.Equal(t, big.NewFloat(5), threshold) + assert.Equal(t, 3, count) +} + +func TestEntriesGetSoftOptOutThresholdEmpty(t *testing.T) { + t.Parallel() + + entries := Entries{ + "thirdaddr": { + IsActive: false, + Validator: &Validator{ + Moniker: "third", + OperatorAddress: "thirdaddr", + VotingPower: big.NewFloat(2), + }, + }, + } + + threshold, count := entries.GetSoftOutOutThreshold(0.05) + assert.Equal(t, big.NewFloat(0), threshold) + assert.Equal(t, 0, count) +} + +func TestEntriesSetTotalVotingPower(t *testing.T) { + t.Parallel() + + entries := Entries{ + "firstaddr": { + IsActive: true, + Validator: &Validator{ + Moniker: "first", + OperatorAddress: "firstaddr", + VotingPower: big.NewFloat(1), + }, + }, + "secondaddr": { + IsActive: true, + Validator: &Validator{ + Moniker: "second", + OperatorAddress: "secondaddr", + VotingPower: big.NewFloat(3), + }, + }, + "thirdaddr": { + IsActive: false, + Validator: &Validator{ + Moniker: "third", + OperatorAddress: "thirdaddr", + VotingPower: big.NewFloat(2), + }, + }, + } + + entries.SetVotingPowerPercent() + assert.Len(t, entries, 3) + assert.InDelta(t, 0.25, entries["firstaddr"].VotingPowerPercent, 0.001) + assert.InDelta(t, 0.75, entries["secondaddr"].VotingPowerPercent, 0.001) + assert.Equal(t, 2, entries["firstaddr"].Rank) + assert.Equal(t, 1, entries["secondaddr"].Rank) + assert.InDelta(t, float64(1), entries["firstaddr"].CumulativeVotingPowerPercent, 0.001) + assert.InDelta(t, 0.75, entries["secondaddr"].CumulativeVotingPowerPercent, 0.001) +} + +func TestEntriesGetByValidatorAddresses(t *testing.T) { + t.Parallel() + + entries := Entries{ + "firstaddr": {Validator: &Validator{OperatorAddress: "firstaddr"}}, + "secondaddr": {Validator: &Validator{OperatorAddress: "secondaddr"}}, + "thirdaddr": {Validator: &Validator{OperatorAddress: "thirdaddr"}}, + } + + filteredEntries := entries.ByValidatorAddresses([]string{"firstaddr", "secondaddr"}) + assert.Len(t, filteredEntries, 2) +} diff --git a/pkg/types/types.go b/pkg/types/types.go index b9ecb65..489ea2d 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -1,10 +1,5 @@ package types -import ( - "math/big" - "sort" -) - type WebsocketEmittable interface { Hash() string } @@ -28,78 +23,6 @@ func (validators Validators) ToMap() ValidatorsMap { return validatorsMap } -func (validators Validators) GetActive() Validators { - activeValidators := make(Validators, 0) - for _, validator := range validators { - if validator.Active() { - activeValidators = append(activeValidators, validator) - } - } - - return activeValidators -} - -func (validators Validators) GetTotalVotingPower() *big.Float { - sum := big.NewFloat(0) - - for _, validator := range validators { - if validator.Active() { - sum.Add(sum, validator.VotingPower) - } - } - - return sum -} - -func (validators Validators) SetVotingPowerPercent() { - totalVP := validators.GetTotalVotingPower() - - activeAndSortedValidators := validators.GetActive() - - // sorting by voting power desc - sort.Slice(activeAndSortedValidators, func(first, second int) bool { - return activeAndSortedValidators[first].VotingPower.Cmp(activeAndSortedValidators[second].VotingPower) > 0 - }) - - var cumulativeVotingPowerPercent float64 = 0 - for index, validator := range activeAndSortedValidators { - percent, _ := new(big.Float).Quo(validator.VotingPower, totalVP).Float64() - validator.VotingPowerPercent = percent - validator.Rank = index + 1 - - cumulativeVotingPowerPercent += percent - validator.CumulativeVotingPowerPercent = cumulativeVotingPowerPercent - } -} - -func (validators Validators) GetSoftOutOutThreshold(softOptOut float64) (*big.Float, int) { - sortedValidators := validators.GetActive() - - if len(sortedValidators) == 0 { - return big.NewFloat(0), 0 - } - - // sorting validators by voting power ascending - sort.Slice(sortedValidators, func(first, second int) bool { - return sortedValidators[first].VotingPower.Cmp(sortedValidators[second].VotingPower) < 0 - }) - - totalVP := validators.GetTotalVotingPower() - threshold := big.NewFloat(0) - - for index, validator := range sortedValidators { - threshold = big.NewFloat(0).Add(threshold, validator.VotingPower) - thresholdPercent := big.NewFloat(0).Quo(threshold, totalVP) - - if thresholdPercent.Cmp(big.NewFloat(softOptOut)) > 0 { - return validator.VotingPower, index + 1 - } - } - - // should've never reached here - return sortedValidators[0].VotingPower, len(sortedValidators) -} - func (validatorsMap ValidatorsMap) ToSlice() Validators { validators := make(Validators, len(validatorsMap)) index := 0 diff --git a/pkg/types/types_test.go b/pkg/types/types_test.go index fdc532e..0edf67a 100644 --- a/pkg/types/types_test.go +++ b/pkg/types/types_test.go @@ -1,7 +1,6 @@ package types import ( - "math/big" "testing" "github.com/stretchr/testify/assert" @@ -40,93 +39,3 @@ func TestValidatorsToSlice(t *testing.T) { assert.Contains(t, monikers, "first", "Validator mismatch!") assert.Contains(t, monikers, "second", "Validator mismatch!") } - -func TestValidatorsGetActive(t *testing.T) { - t.Parallel() - - validators := Validators{ - {Moniker: "first", OperatorAddress: "firstaddr", Status: 3}, - {Moniker: "second", OperatorAddress: "secondaddr", Status: 1}, - } - - activeValidators := validators.GetActive() - assert.Len(t, activeValidators, 1) -} - -func TestValidatorsGetTotalVotingPower(t *testing.T) { - t.Parallel() - - validators := Validators{ - {Moniker: "first", OperatorAddress: "firstaddr", Status: 3, VotingPower: big.NewFloat(1)}, - {Moniker: "second", OperatorAddress: "secondaddr", Status: 3, VotingPower: big.NewFloat(2)}, - {Moniker: "third", OperatorAddress: "thirdaddr", Status: 1, VotingPower: big.NewFloat(3)}, - } - - totalVotingPower := validators.GetTotalVotingPower() - assert.Equal(t, totalVotingPower, big.NewFloat(3)) -} - -func TestValidatorsSetTotalVotingPower(t *testing.T) { - t.Parallel() - - validators := Validators{ - {Moniker: "first", OperatorAddress: "firstaddr", Status: 3, VotingPower: big.NewFloat(1)}, - {Moniker: "second", OperatorAddress: "secondaddr", Status: 3, VotingPower: big.NewFloat(3)}, - {Moniker: "third", OperatorAddress: "thirdaddr", Status: 1, VotingPower: big.NewFloat(2)}, - } - - validators.SetVotingPowerPercent() - assert.Len(t, validators, 3) - assert.InDelta(t, 0.25, validators[0].VotingPowerPercent, 0.001) - assert.InDelta(t, 0.75, validators[1].VotingPowerPercent, 0.001) - assert.Equal(t, 2, validators[0].Rank) - assert.Equal(t, 1, validators[1].Rank) - assert.InDelta(t, float64(1), validators[0].CumulativeVotingPowerPercent, 0.001) - assert.InDelta(t, 0.75, validators[1].CumulativeVotingPowerPercent, 0.001) -} - -func TestValidatorsGetSoftOptOutThresholdAchievable(t *testing.T) { - t.Parallel() - - // 3 active validators, with 80%, 15% and 5% vp, with soft-opt-out as 5% - // top 2 should be required to sign and threshold should be 15 - validators := Validators{ - {Moniker: "first", OperatorAddress: "firstaddr", Status: 3, VotingPower: big.NewFloat(80)}, - {Moniker: "second", OperatorAddress: "secondaddr", Status: 3, VotingPower: big.NewFloat(15)}, - {Moniker: "third", OperatorAddress: "thirdaddr", Status: 1, VotingPower: big.NewFloat(2)}, - {Moniker: "fourth", OperatorAddress: "fourthaddr", Status: 3, VotingPower: big.NewFloat(5)}, - } - - threshold, count := validators.GetSoftOutOutThreshold(0.05) - assert.Equal(t, big.NewFloat(15), threshold) - assert.Equal(t, 2, count) -} - -func TestValidatorsGetSoftOptOutThresholdNotAchievable(t *testing.T) { - t.Parallel() - - // 3 active validators, with 80%, 15% and 5% vp, with not achievable threshold (like -0.05) - // it should require all active validators to be signing - validators := Validators{ - {Moniker: "first", OperatorAddress: "firstaddr", Status: 3, VotingPower: big.NewFloat(80)}, - {Moniker: "second", OperatorAddress: "secondaddr", Status: 3, VotingPower: big.NewFloat(15)}, - {Moniker: "third", OperatorAddress: "thirdaddr", Status: 1, VotingPower: big.NewFloat(2)}, - {Moniker: "fourth", OperatorAddress: "fourthaddr", Status: 3, VotingPower: big.NewFloat(5)}, - } - - threshold, count := validators.GetSoftOutOutThreshold(1) - assert.Equal(t, big.NewFloat(5), threshold) - assert.Equal(t, 3, count) -} - -func TestValidatorsGetSoftOptOutThresholdEmpty(t *testing.T) { - t.Parallel() - - validators := Validators{ - {Moniker: "third", OperatorAddress: "thirdaddr", Status: 1, VotingPower: big.NewFloat(2)}, - } - - threshold, count := validators.GetSoftOutOutThreshold(0.05) - assert.Equal(t, big.NewFloat(0), threshold) - assert.Equal(t, 0, count) -} diff --git a/pkg/types/validator.go b/pkg/types/validator.go index f821f34..5fe0cda 100644 --- a/pkg/types/validator.go +++ b/pkg/types/validator.go @@ -1,7 +1,6 @@ package types import ( - "main/pkg/constants" "math/big" ) @@ -20,7 +19,6 @@ type Validator struct { ConsensusAddressValcons string OperatorAddress string Commission float64 - Status int32 Jailed bool SigningInfo *SigningInfo @@ -28,9 +26,4 @@ type Validator struct { VotingPowerPercent float64 CumulativeVotingPowerPercent float64 Rank int - NeedsToSign bool // for consumer chains -} - -func (v *Validator) Active() bool { - return v.Status == constants.ValidatorBonded } diff --git a/templates/discord/Params.md b/templates/discord/Params.md index c2e6d11..f5bc175 100644 --- a/templates/discord/Params.md +++ b/templates/discord/Params.md @@ -11,6 +11,7 @@ Approximate time to go to jail when missing all blocks: {{ .FormatTimeToJail }} {{ if .Config.IsConsumer.Bool -}} The chain is an ICS consumer chain. Soft opt-out percent is at {{ .FormatSoftOptOut }}%. +{{ .ConsumerOptOutValidators }} bottom validators are not required to sign blocks. Top {{ .GetConsumerRequiredValidators }} are required to sign blocks. {{- else -}} The chain is a sovereign chain. diff --git a/templates/telegram/Status.html b/templates/telegram/Status.html index 00945a8..f045f09 100644 --- a/templates/telegram/Status.html +++ b/templates/telegram/Status.html @@ -3,7 +3,7 @@ {{- range .Entries }} {{ if .Validator.Jailed -}} {{ SerializeLink .Link }}: jailed -{{- else if not .Validator.Active -}} +{{- else if not .IsActive -}} {{ SerializeLink .Link }}: not in the active set {{- else if .Error -}} {{ SerializeLink .Link }}: error getting validators missed blocks: {{ .Error }}