Skip to content

Commit

Permalink
[offchain][effective][validator][rpc] Give reason why booted from com…
Browse files Browse the repository at this point in the history
…mitteee, move effective stake in rpc outside metrics (#2699)
  • Loading branch information
fxfactorial authored Apr 3, 2020
1 parent d21f68d commit 56d19d2
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 47 deletions.
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

0 comments on commit 56d19d2

Please sign in to comment.