Skip to content
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

[offchain][effective][validator][rpc] Give reason why booted from committee #2699

Merged
merged 1 commit into from
Apr 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 63 additions & 27 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ import (
"github.com/harmony-one/harmony/shard"
"github.com/harmony-one/harmony/shard/committee"
"github.com/harmony-one/harmony/staking/apr"
"github.com/harmony-one/harmony/staking/availability"
"github.com/harmony-one/harmony/staking/effective"
"github.com/harmony-one/harmony/staking/slash"
staking "github.com/harmony-one/harmony/staking/types"
lru "github.com/hashicorp/golang-lru"
Expand Down Expand Up @@ -2289,7 +2291,29 @@ func (bc *BlockChain) UpdateValidatorVotingPower(
return shard.ErrSuperCommitteeNil
}

rosters := make([]*votepower.Roster, len(newEpochSuperCommittee.Shards))
rosters, bootedFromSuperCommittee :=
make([]*votepower.Roster, len(newEpochSuperCommittee.Shards)),
map[common.Address]struct{}{}

existing, replacing :=
currentEpochSuperCommittee.StakedValidators(),
newEpochSuperCommittee.StakedValidators()

// TODO could also keep track of the BLS keys which
// lost a slot because just losing slots doesn't mean that the
// validator was booted, just that some of their keys lost slots

for currentValidator := range existing.LookupSet {
if _, keptSlot := replacing.LookupSet[currentValidator]; !keptSlot {
bootedFromSuperCommittee[currentValidator] = struct{}{}
// NOTE Think carefully about when time comes to delete offchain things
// TODO Someone: collect and then delete every 30 epochs
// rawdb.DeleteValidatorSnapshot(
// bc.db, currentValidator, currentEpochSuperCommittee.Epoch,
// )
// rawdb.DeleteValidatorStats(bc.db, currentValidator)
}
}

for i := range newEpochSuperCommittee.Shards {
subCommittee := &newEpochSuperCommittee.Shards[i]
Expand All @@ -2307,7 +2331,9 @@ func (bc *BlockChain) UpdateValidatorVotingPower(
}
rosters[i] = roster
}

networkWide := votepower.AggregateRosters(rosters)

for key, value := range networkWide {
stats, err := rawdb.ReadValidatorStats(bc.db, key)
if err != nil {
Expand Down Expand Up @@ -2347,44 +2373,54 @@ func (bc *BlockChain) UpdateValidatorVotingPower(
utils.Logger().Debug().Err(err).Msg("issue with compute of apr")
}

snapshot, err := bc.ReadValidatorSnapshotAtEpoch(
currentEpochSuperCommittee.Epoch, wrapper.Address,
)

if err != nil {
return err
}

computed := availability.ComputeCurrentSigning(snapshot, wrapper)

if _, wasBooted := bootedFromSuperCommittee[wrapper.Address]; wasBooted {
stats.BootedStatus = effective.LostEPoSAuction
}

if computed.IsBelowThreshold {
stats.BootedStatus = effective.InsufficientUptimeDuringEpoch
}

if slash.IsBanned(wrapper) {
stats.BootedStatus = effective.BannedForDoubleSigning
}

if err := rawdb.WriteValidatorStats(
batch, key, stats,
); err != nil {
return err
}
}

existing, replacing :=
currentEpochSuperCommittee.StakedValidators(),
newEpochSuperCommittee.StakedValidators()
for currentValidator := range existing.LookupSet {
if _, keptSlot := replacing.LookupSet[currentValidator]; !keptSlot {
// TODO Someone: collect and then delete every 30 epochs
// rawdb.DeleteValidatorSnapshot(
// bc.db, currentValidator, currentEpochSuperCommittee.Epoch,
// )
rawdb.DeleteValidatorStats(bc.db, currentValidator)
}
}

return nil
}

// deleteValidatorSnapshots deletes the snapshot staking information of given validator address
// TODO: delete validator snapshots from X epochs ago
func (bc *BlockChain) deleteValidatorSnapshots(addrs []common.Address) error {
batch := bc.db.NewBatch()
for i := range addrs {
rawdb.DeleteValidatorSnapshot(batch, addrs[i], bc.CurrentBlock().Epoch())
}
if err := batch.Write(); err != nil {
return err
}
for i := range addrs {
bc.validatorCache.Remove("validator-snapshot-" + string(addrs[i].Bytes()))
}
return nil
}
// NOTE Use when needed but don't compile at all until then
// func (bc *BlockChain) deleteValidatorSnapshots(addrs []common.Address) error {
// batch := bc.db.NewBatch()
// for i := range addrs {
// rawdb.DeleteValidatorSnapshot(batch, addrs[i], bc.CurrentBlock().Epoch())
// }
// if err := batch.Write(); err != nil {
// return err
// }
// for i := range addrs {
// bc.validatorCache.Remove("validator-snapshot-" + string(addrs[i].Bytes()))
// }
// return nil
// }

// UpdateValidatorSnapshots updates the content snapshot of all validators
// Note: this should only be called within the blockchain insert process.
Expand Down
9 changes: 6 additions & 3 deletions core/offchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,10 +248,13 @@ func (bc *BlockChain) CommitOffChainData(
addr common.Address
stats *staking.ValidatorStats
}
sortedStats := []t{}
for key, value := range tempValidatorStats {
sortedStats = append(sortedStats, t{key, value})

sortedStats, i := make([]t, len(tempValidatorStats)), 0
for key := range tempValidatorStats {
sortedStats[i] = t{key, tempValidatorStats[key]}
i++
}

sort.SliceStable(
sortedStats,
func(i, j int) bool {
Expand Down
12 changes: 11 additions & 1 deletion hmy/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,8 @@ func (b *APIBackend) GetValidatorInformation(
EPoSStatus: effective.ValidatorStatus(
inCommittee, wrapper.Status,
).String(),
EPoSWinningStake: nil,
BootedStatus: nil,
Lifetime: &staking.AccumulatedOverLifetime{
wrapper.BlockReward,
wrapper.Counters,
Expand All @@ -384,7 +386,9 @@ func (b *APIBackend) GetValidatorInformation(
computed := availability.ComputeCurrentSigning(
snapshot, wrapper,
)
beaconChainBlocks := uint64(b.hmy.BeaconChain().CurrentBlock().Header().Number().Int64()) % shard.Schedule.BlocksPerEpoch()
beaconChainBlocks := uint64(
b.hmy.BeaconChain().CurrentBlock().Header().Number().Int64(),
) % shard.Schedule.BlocksPerEpoch()
computed.BlocksLeftInEpoch = shard.Schedule.BlocksPerEpoch() - beaconChainBlocks

stats, err := bc.ReadValidatorStats(addr)
Expand All @@ -399,6 +403,12 @@ func (b *APIBackend) GetValidatorInformation(
CurrentSigningPercentage: *computed,
}
defaultReply.ComputedMetrics = stats
defaultReply.EPoSWinningStake = &stats.TotalEffectiveStake
}

if !defaultReply.CurrentlyInCommittee {
reason := stats.BootedStatus.String()
defaultReply.BootedStatus = &reason
}

return defaultReply, nil
Expand Down
35 changes: 22 additions & 13 deletions shard/shard_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,29 +150,28 @@ func EncodeWrapper(shardState State, isStaking bool) ([]byte, error) {
return data, err
}

// StakedSlots gives overview of subset of shard state that is
// coming via an stake, that is, view epos
// StakedSlots gives overview of members
// in a subcommittee (aka a shard)
type StakedSlots struct {
CountStakedValidator int
CountStakedBLSKey int
Addrs []common.Address
LookupSet map[common.Address]struct{}
TotalEffectiveStaked numeric.Dec
}

// StakedValidators filters for non-harmony operated nodes,
// returns (
// totalStakedValidatorsCount, totalStakedBLSKeys,
// addrsOnNetworkSlice, addrsOnNetworkSet,
// )
// StakedValidators ..
func (c Committee) StakedValidators() *StakedSlots {
countStakedValidator, countStakedBLSKey := 0, 0
networkWideSlice, networkWideSet :=
[]common.Address{}, map[common.Address]struct{}{}
for _, slot := range c.Slots {
totalEffectiveStake := numeric.ZeroDec()

for _, slot := range c.Slots {
// an external validator,
// non-nil EffectiveStake is how we known
if addr := slot.EcdsaAddress; slot.EffectiveStake != nil {
totalEffectiveStake = totalEffectiveStake.Add(*slot.EffectiveStake)
countStakedBLSKey++
if _, seen := networkWideSet[addr]; !seen {
countStakedValidator++
Expand All @@ -187,20 +186,28 @@ func (c Committee) StakedValidators() *StakedSlots {
CountStakedBLSKey: countStakedBLSKey,
Addrs: networkWideSlice,
LookupSet: networkWideSet,
TotalEffectiveStaked: totalEffectiveStake,
}
}

// StakedValidators filters for non-harmony operated nodes,
// returns (
// totalStakedValidatorsCount, totalStakedBLSKeys,
// addrsOnNetworkSlice, addrsOnNetworkSet,
// )
// TODO refactor with and update corresponding places
// func (ss *State) StakedValidators() []*StakedSlots {
// networkWide := make([]*StakedSlots, len(ss.Shards))
// for i := range ss.Shards {
// networkWide[i] = ss.Shards[i].StakedValidators()
// }
// return networkWide
// }

// StakedValidators here is supercommittee wide
func (ss *State) StakedValidators() *StakedSlots {
countStakedValidator, countStakedBLSKey := 0, 0
networkWideSlice, networkWideSet :=
[]common.Address{},
map[common.Address]struct{}{}

totalEffectiveStake := numeric.ZeroDec()

for i := range ss.Shards {
shard := ss.Shards[i]
for j := range shard.Slots {
Expand All @@ -209,6 +216,7 @@ func (ss *State) StakedValidators() *StakedSlots {
// an external validator,
// non-nil EffectiveStake is how we known
if addr := slot.EcdsaAddress; slot.EffectiveStake != nil {
totalEffectiveStake = totalEffectiveStake.Add(*slot.EffectiveStake)
countStakedBLSKey++
if _, seen := networkWideSet[addr]; !seen {
countStakedValidator++
Expand All @@ -224,6 +232,7 @@ func (ss *State) StakedValidators() *StakedSlots {
CountStakedBLSKey: countStakedBLSKey,
Addrs: networkWideSlice,
LookupSet: networkWideSet,
TotalEffectiveStaked: totalEffectiveStake,
}
}

Expand Down
1 change: 0 additions & 1 deletion staking/effective/calculate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ const eposTestingFile = "epos.json"

var (
testingNumber = 20
testingSlots slotsData
testingPurchases []SlotPurchase
expectedMedian numeric.Dec
maxAccountGen = int64(98765654323123134)
Expand Down
33 changes: 32 additions & 1 deletion staking/effective/eligible.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,14 @@ const (
Elected
)

const (
doubleSigningBanned = "banned forever from network because was caught double-signing"
)

func (c Candidacy) String() string {
switch c {
case ForeverBanned:
return "banned forever from network because was caught double-signing"
return doubleSigningBanned
case Candidate:
return "eligible to be elected next epoch"
case NotCandidate:
Expand All @@ -69,3 +73,30 @@ func ValidatorStatus(currentlyInCommittee bool, status Eligibility) Candidacy {
return Unknown
}
}

// BootedStatus ..
type BootedStatus byte

const (
// NotBooted ..
NotBooted BootedStatus = iota
// LostEPoSAuction ..
LostEPoSAuction
// InsufficientUptimeDuringEpoch ..
InsufficientUptimeDuringEpoch
// BannedForDoubleSigning ..
BannedForDoubleSigning
)

func (r BootedStatus) String() string {
switch r {
case LostEPoSAuction:
return "lost epos auction"
case InsufficientUptimeDuringEpoch:
return "bad uptime"
case BannedForDoubleSigning:
return doubleSigningBanned
default:
return "not booted"
}
}
5 changes: 5 additions & 0 deletions staking/slash/double-sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,11 @@ func Apply(
return slashDiff, nil
}

// IsBanned ..
func IsBanned(wrapper *staking.ValidatorWrapper) bool {
return wrapper.Status == effective.Banned
}

// Rate is the slashing % rate
func Rate(votingPower *votepower.Roster, records Records) numeric.Dec {
rate := numeric.ZeroDec()
Expand Down
7 changes: 6 additions & 1 deletion staking/types/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ func NewEmptyStats() *ValidatorStats {
numeric.ZeroDec(),
numeric.ZeroDec(),
[]VoteWithCurrentEpochEarning{},
effective.NotBooted,
}
}

Expand All @@ -139,6 +140,8 @@ type ValidatorRPCEnchanced struct {
TotalDelegated *big.Int `json:"total-delegation"`
CurrentlyInCommittee bool `json:"currently-in-committee"`
EPoSStatus string `json:"epos-status"`
EPoSWinningStake *numeric.Dec `json:"epos-winning-stake"`
BootedStatus *string `json:"booted-status"`
Lifetime *AccumulatedOverLifetime `json:"lifetime"`
}

Expand Down Expand Up @@ -178,9 +181,11 @@ type ValidatorStats struct {
// APR ..
APR numeric.Dec `json:"-"`
// TotalEffectiveStake is the total effective stake this validator has
TotalEffectiveStake numeric.Dec `json:"total-effective-stake"`
TotalEffectiveStake numeric.Dec `json:"-"`
// MetricsPerShard ..
MetricsPerShard []VoteWithCurrentEpochEarning `json:"by-bls-key"`
// BootedStatus
BootedStatus effective.BootedStatus `json:"boot-from-committee-status"`
}

func (s ValidatorStats) String() string {
Expand Down