Fix create validator balance reduce bug; Add LastEpochInCommittee (#2029)

* Fix undelegation non-pointer modification bug

* Fix lint

* fix import

* Fix create validator balance reduce bug; Add LastEpochInCommittee
pull/2030/head
Rongjian Lan 5 years ago committed by Edgar Aroutiounian
parent 1a9f769595
commit e7cd387677
  1. 4
      consensus/quorum/one-node-staked-vote_test.go
  2. 31
      consensus/votepower/roster.go
  3. 10
      consensus/votepower/roster_test.go
  4. 4
      core/blockchain.go
  5. 6
      core/state_transition.go
  6. 36
      internal/chain/engine.go
  7. 10
      shard/shard_state.go
  8. 4
      shard/shard_state_test.go
  9. 6
      staking/types/delegation.go
  10. 10
      staking/types/validator.go
  11. 2
      staking/types/validator_test.go

@ -54,7 +54,7 @@ func setupBaseCase() (Decider, *TallyResult, shard.SlotList, map[string]secretKe
for i := 0; i < quorumNodes; i++ {
newSlot, sKey := generateRandomSlot()
if i < 50 {
newSlot.TotalStake = nil
newSlot.EffectiveStake = nil
sKeys[hmy][newSlot.BlsPublicKey] = sKey
} else {
sKeys[reg][newSlot.BlsPublicKey] = sKey
@ -82,7 +82,7 @@ func setupEdgeCase() (Decider, *TallyResult, shard.SlotList, secretKeyMap) {
for i := 0; i < quorumNodes; i++ {
newSlot, sKey := generateRandomSlot()
if i < 33 {
newSlot.TotalStake = nil
newSlot.EffectiveStake = nil
sKeys[newSlot.BlsPublicKey] = sKey
}
slotList = append(slotList, newSlot)

@ -41,11 +41,9 @@ type Roster struct {
// Staker ..
type Staker struct {
AverageVotingPower numeric.Dec
AverageEffectiveStake numeric.Dec
TotalEffectiveStake numeric.Dec
VotingPower []staking.VotePerShard
BLSPublicKeysOwned []staking.KeysPerShard
TotalEffectiveStake numeric.Dec
VotingPower []staking.VotePerShard
BLSPublicKeysOwned []staking.KeysPerShard
}
// RosterPerShard ..
@ -57,7 +55,6 @@ type RosterPerShard struct {
// AggregateRosters ..
func AggregateRosters(rosters []RosterPerShard) map[common.Address]Staker {
result := map[common.Address]Staker{}
c := int64(len(rosters))
sort.SliceStable(rosters,
func(i, j int) bool { return rosters[i].ShardID < rosters[j].ShardID },
)
@ -70,9 +67,6 @@ func AggregateRosters(rosters []RosterPerShard) map[common.Address]Staker {
payload.TotalEffectiveStake = payload.TotalEffectiveStake.Add(
value.EffectiveStake,
)
payload.AverageVotingPower = payload.AverageVotingPower.Add(
value.EffectivePercent,
)
payload.VotingPower = append(payload.VotingPower,
staking.VotePerShard{roster.ShardID, value.EffectivePercent},
)
@ -81,9 +75,7 @@ func AggregateRosters(rosters []RosterPerShard) map[common.Address]Staker {
)
} else {
result[value.EarningAccount] = Staker{
AverageVotingPower: value.EffectivePercent,
AverageEffectiveStake: numeric.ZeroDec(),
TotalEffectiveStake: value.EffectiveStake,
TotalEffectiveStake: value.EffectiveStake,
VotingPower: []staking.VotePerShard{
{roster.ShardID, value.EffectivePercent},
},
@ -95,11 +87,6 @@ func AggregateRosters(rosters []RosterPerShard) map[common.Address]Staker {
}
}
for _, value := range result {
value.AverageEffectiveStake = value.TotalEffectiveStake.QuoInt64(c)
value.AverageVotingPower = value.AverageVotingPower.QuoInt64(c)
}
return result
}
@ -128,11 +115,11 @@ func (r *Roster) JSON() string {
func Compute(staked shard.SlotList) (*Roster, error) {
roster := NewRoster()
for i := range staked {
if staked[i].TotalStake == nil {
if staked[i].EffectiveStake == nil {
roster.HmySlotCount++
} else {
roster.RawStakedTotal = roster.RawStakedTotal.Add(
*staked[i].TotalStake,
*staked[i].EffectiveStake,
)
}
}
@ -153,10 +140,10 @@ func Compute(staked shard.SlotList) (*Roster, error) {
}
// Real Staker
if staked[i].TotalStake != nil {
if staked[i].EffectiveStake != nil {
member.IsHarmonyNode = false
member.EffectiveStake = member.EffectiveStake.Add(*staked[i].TotalStake)
member.EffectivePercent = staked[i].TotalStake.
member.EffectiveStake = member.EffectiveStake.Add(*staked[i].EffectiveStake)
member.EffectivePercent = staked[i].EffectiveStake.
Quo(roster.RawStakedTotal).
Mul(StakersShare)
theirPercentage = theirPercentage.Add(member.EffectivePercent)

@ -29,14 +29,14 @@ var (
func init() {
for i := 0; i < harmonyNodes; i++ {
newSlot := generateRandomSlot()
newSlot.TotalStake = nil
newSlot.EffectiveStake = nil
slotList = append(slotList, newSlot)
}
totalStake = numeric.ZeroDec()
for j := 0; j < stakedNodes; j++ {
newSlot := generateRandomSlot()
totalStake = totalStake.Add(*newSlot.TotalStake)
totalStake = totalStake.Add(*newSlot.EffectiveStake)
slotList = append(slotList, newSlot)
}
}
@ -67,9 +67,9 @@ func TestCompute(t *testing.T) {
EffectiveStake: numeric.ZeroDec(),
}
// Not Harmony node
if slot.TotalStake != nil {
newMember.EffectiveStake = *slot.TotalStake
newMember.EffectivePercent = slot.TotalStake.Quo(expectedRoster.RawStakedTotal).Mul(StakersShare)
if slot.EffectiveStake != nil {
newMember.EffectiveStake = *slot.EffectiveStake
newMember.EffectivePercent = slot.EffectiveStake.Quo(expectedRoster.RawStakedTotal).Mul(StakersShare)
expectedRoster.TheirVotingPowerTotalPercentage = expectedRoster.TheirVotingPowerTotalPercentage.Add(newMember.EffectivePercent)
} else {
// Harmony node

@ -1178,7 +1178,7 @@ func (bc *BlockChain) WriteBlockWithState(
shard := newShardState.Shards[i]
for j := range shard.Slots {
slot := shard.Slots[j]
if slot.TotalStake != nil { // For external validator
if slot.EffectiveStake != nil { // For external validator
_, ok := processed[slot.EcdsaAddress]
if !ok {
processed[slot.EcdsaAddress] = struct{}{}
@ -1294,7 +1294,7 @@ func (bc *BlockChain) WriteBlockWithState(
members := []*bls2.PublicKey{}
for _, slot := range committee.Slots {
if slot.TotalStake != nil {
if slot.EffectiveStake != nil {
pubKey := &bls2.PublicKey{}
err := pubKey.Deserialize(slot.BlsPublicKey[:])
if err != nil {

@ -380,6 +380,10 @@ func (st *StateTransition) applyCreateValidatorTx(createValidator *staking.Creat
return errors.Wrapf(errValidatorExist, common2.MustAddressToBech32(val))
}
if !CanTransfer(st.state, createValidator.ValidatorAddress, createValidator.Amount) {
return errInsufficientBalanceForStake
}
v, err := staking.CreateValidatorFromNewMsg(createValidator, blockNum)
if err != nil {
return err
@ -395,6 +399,8 @@ func (st *StateTransition) applyCreateValidatorTx(createValidator *staking.Creat
}
st.state.SetValidatorFlag(v.Address)
st.state.SubBalance(v.Address, createValidator.Amount)
return nil
}

@ -279,22 +279,48 @@ func (e *engineImpl) Finalize(
if header.ShardID() == shard.BeaconChainShardID && len(header.ShardState()) > 0 {
validators, err := chain.ReadValidatorList()
if err != nil {
return nil, nil, ctxerror.New("failed to read active validators").WithCause(err)
return nil, nil, ctxerror.New("[Finalize] failed to read active validators").WithCause(err)
}
// Payout undelegated/unlocked tokens
for _, validator := range validators {
wrapper := state.GetStakingInfo(validator)
if wrapper != nil {
for i := range wrapper.Delegations {
delegation := &wrapper.Delegations[i]
totalWithdraw := delegation.RemoveUnlockedUndelegations(header.Epoch())
totalWithdraw := delegation.RemoveUnlockedUndelegations(header.Epoch(), wrapper.LastEpochInCommittee)
state.AddBalance(delegation.DelegatorAddress, totalWithdraw)
}
if err := state.UpdateStakingInfo(validator, wrapper); err != nil {
return nil, nil, ctxerror.New("failed update validator info").WithCause(err)
return nil, nil, ctxerror.New("[Finalize] failed update validator info").WithCause(err)
}
} else {
err = errors.New("validator came back empty " + common2.MustAddressToBech32(validator))
return nil, nil, ctxerror.New("failed getting validator info").WithCause(err)
err = errors.New("[Finalize] validator came back empty " + common2.MustAddressToBech32(validator))
return nil, nil, ctxerror.New("[Finalize] failed getting validator info").WithCause(err)
}
}
// Set the LastEpochInCommittee field for all external validators in the upcoming epoch.
newShardState, err := header.GetShardState()
if err != nil {
return nil, nil, ctxerror.New("[Finalize] failed to read shard state").WithCause(err)
}
processed := make(map[common.Address]struct{})
for i := range newShardState.Shards {
shard := newShardState.Shards[i]
for j := range shard.Slots {
slot := shard.Slots[j]
if slot.EffectiveStake != nil { // For external validator
_, ok := processed[slot.EcdsaAddress]
if !ok {
processed[slot.EcdsaAddress] = struct{}{}
wrapper := state.GetStakingInfo(slot.EcdsaAddress)
wrapper.LastEpochInCommittee = newShardState.Epoch
if err := state.UpdateStakingInfo(slot.EcdsaAddress, wrapper); err != nil {
return nil, nil, ctxerror.New("[Finalize] failed update validator info").WithCause(err)
}
}
}
}
}
}

@ -43,7 +43,7 @@ type Slot struct {
EcdsaAddress common.Address `json:"ecdsa-address"`
BlsPublicKey BlsPublicKey `json:"bls-pubkey"`
// nil means our node, 0 means not active, > 0 means staked node
TotalStake *numeric.Dec `json:"total-stake" rlp:"nil"`
EffectiveStake *numeric.Dec `json:"effective-stake" rlp:"nil"`
}
// SlotList is a list of Slot.
@ -155,7 +155,7 @@ func (ss *State) JSON() string {
for j := range ss.Shards[i].Slots {
n := ss.Shards[i].Slots[j]
dump[i].NodeList[j].BlsPublicKey = n.BlsPublicKey
dump[i].NodeList[j].TotalStake = n.TotalStake
dump[i].NodeList[j].EffectiveStake = n.EffectiveStake
dump[i].NodeList[j].EcdsaAddress = common2.MustAddressToBech32(n.EcdsaAddress)
}
}
@ -332,8 +332,8 @@ func (n Slot) Serialize() []byte {
func (n Slot) String() string {
total := "nil"
if n.TotalStake != nil {
total = n.TotalStake.String()
if n.EffectiveStake != nil {
total = n.EffectiveStake.String()
}
return "ECDSA: " + common2.MustAddressToBech32(n.EcdsaAddress) + ", BLS: " + hex.EncodeToString(n.BlsPublicKey[:]) + ", TotalStake: " + total
return "ECDSA: " + common2.MustAddressToBech32(n.EcdsaAddress) + ", BLS: " + hex.EncodeToString(n.BlsPublicKey[:]) + ", EffectiveStake: " + total
}

@ -22,8 +22,8 @@ var (
)
const (
json1 = `[{"shard-id":0,"member-count":4,"subcommittee":[{"bls-pubkey":"72616e646f6d206b65792031000000000000000000000000000000000000000000000000000000000000000000000000","total-stake":null,"ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"},{"bls-pubkey":"72616e646f6d206b65792032000000000000000000000000000000000000000000000000000000000000000000000000","total-stake":null,"ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"},{"bls-pubkey":"72616e646f6d206b65792033000000000000000000000000000000000000000000000000000000000000000000000000","total-stake":null,"ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"},{"bls-pubkey":"72616e646f6d206b65792034000000000000000000000000000000000000000000000000000000000000000000000000","total-stake":null,"ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"}]},{"shard-id":1,"member-count":2,"subcommittee":[{"bls-pubkey":"72616e646f6d206b65792035000000000000000000000000000000000000000000000000000000000000000000000000","total-stake":null,"ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"},{"bls-pubkey":"72616e646f6d206b65792036000000000000000000000000000000000000000000000000000000000000000000000000","total-stake":null,"ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"}]}]`
json2 = `[{"shard-id":0,"member-count":5,"subcommittee":[{"bls-pubkey":"72616e646f6d206b65792031000000000000000000000000000000000000000000000000000000000000000000000000","total-stake":null,"ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"},{"bls-pubkey":"72616e646f6d206b65792032000000000000000000000000000000000000000000000000000000000000000000000000","total-stake":null,"ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"},{"bls-pubkey":"72616e646f6d206b65792033000000000000000000000000000000000000000000000000000000000000000000000000","total-stake":"10.000000000000000000","ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"},{"bls-pubkey":"72616e646f6d206b65792035000000000000000000000000000000000000000000000000000000000000000000000000","total-stake":null,"ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"},{"bls-pubkey":"72616e646f6d206b65792031000000000000000000000000000000000000000000000000000000000000000000000000","total-stake":"45.123000000000000000","ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"}]},{"shard-id":1,"member-count":4,"subcommittee":[{"bls-pubkey":"72616e646f6d206b65792032000000000000000000000000000000000000000000000000000000000000000000000000","total-stake":"10.000000000000000000","ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"},{"bls-pubkey":"72616e646f6d206b65792033000000000000000000000000000000000000000000000000000000000000000000000000","total-stake":null,"ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"},{"bls-pubkey":"72616e646f6d206b65792034000000000000000000000000000000000000000000000000000000000000000000000000","total-stake":null,"ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"},{"bls-pubkey":"72616e646f6d206b65792035000000000000000000000000000000000000000000000000000000000000000000000000","total-stake":"45.123000000000000000","ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"}]}]`
json1 = `[{"shard-id":0,"member-count":4,"subcommittee":[{"bls-pubkey":"72616e646f6d206b65792031000000000000000000000000000000000000000000000000000000000000000000000000","effective-stake":null,"ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"},{"bls-pubkey":"72616e646f6d206b65792032000000000000000000000000000000000000000000000000000000000000000000000000","effective-stake":null,"ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"},{"bls-pubkey":"72616e646f6d206b65792033000000000000000000000000000000000000000000000000000000000000000000000000","effective-stake":null,"ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"},{"bls-pubkey":"72616e646f6d206b65792034000000000000000000000000000000000000000000000000000000000000000000000000","effective-stake":null,"ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"}]},{"shard-id":1,"member-count":2,"subcommittee":[{"bls-pubkey":"72616e646f6d206b65792035000000000000000000000000000000000000000000000000000000000000000000000000","effective-stake":null,"ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"},{"bls-pubkey":"72616e646f6d206b65792036000000000000000000000000000000000000000000000000000000000000000000000000","effective-stake":null,"ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"}]}]`
json2 = `[{"shard-id":0,"member-count":5,"subcommittee":[{"bls-pubkey":"72616e646f6d206b65792031000000000000000000000000000000000000000000000000000000000000000000000000","effective-stake":null,"ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"},{"bls-pubkey":"72616e646f6d206b65792032000000000000000000000000000000000000000000000000000000000000000000000000","effective-stake":null,"ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"},{"bls-pubkey":"72616e646f6d206b65792033000000000000000000000000000000000000000000000000000000000000000000000000","effective-stake":"10.000000000000000000","ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"},{"bls-pubkey":"72616e646f6d206b65792035000000000000000000000000000000000000000000000000000000000000000000000000","effective-stake":null,"ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"},{"bls-pubkey":"72616e646f6d206b65792031000000000000000000000000000000000000000000000000000000000000000000000000","effective-stake":"45.123000000000000000","ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"}]},{"shard-id":1,"member-count":4,"subcommittee":[{"bls-pubkey":"72616e646f6d206b65792032000000000000000000000000000000000000000000000000000000000000000000000000","effective-stake":"10.000000000000000000","ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"},{"bls-pubkey":"72616e646f6d206b65792033000000000000000000000000000000000000000000000000000000000000000000000000","effective-stake":null,"ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"},{"bls-pubkey":"72616e646f6d206b65792034000000000000000000000000000000000000000000000000000000000000000000000000","effective-stake":null,"ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"},{"bls-pubkey":"72616e646f6d206b65792035000000000000000000000000000000000000000000000000000000000000000000000000","effective-stake":"45.123000000000000000","ecdsa-address":"one1qqqqqqqqqqqqqqqqqqqqqqq423ktdf2pznf238"}]}]`
)
func init() {

@ -106,11 +106,13 @@ func (d *Delegation) DeleteEntry(epoch *big.Int) {
}
// RemoveUnlockedUndelegations removes all fully unlocked undelegations and returns the total sum
func (d *Delegation) RemoveUnlockedUndelegations(curEpoch *big.Int) *big.Int {
func (d *Delegation) RemoveUnlockedUndelegations(curEpoch, lastEpochInCommittee *big.Int) *big.Int {
totalWithdraw := big.NewInt(0)
count := 0
for j := range d.Undelegations {
if big.NewInt(0).Sub(curEpoch, d.Undelegations[j].Epoch).Int64() > LockPeriodInEpoch { // need to wait at least 7 epochs to withdraw;
if big.NewInt(0).Sub(curEpoch, d.Undelegations[j].Epoch).Int64() > LockPeriodInEpoch ||
big.NewInt(0).Sub(curEpoch, lastEpochInCommittee).Int64() > LockPeriodInEpoch {
// need to wait at least 7 epochs to withdraw; or the validator has been out of committee for 7 epochs
totalWithdraw.Add(totalWithdraw, d.Undelegations[j].Amount)
count++
} else {

@ -80,13 +80,13 @@ type Validator struct {
Address common.Address
// The BLS public key of the validator for consensus
SlotPubKeys []shard.BlsPublicKey
// TODO Remove this field
UnbondingHeight *big.Int
// The number of the last epoch this validator is selected in committee (0 means never selected)
LastEpochInCommittee *big.Int
// validator's self declared minimum self delegation
MinSelfDelegation *big.Int
// maximum total delegation allowed
MaxTotalDelegation *big.Int
// Is the validator active in the validating process or not
// Is the validator active in participating committee selection process or not
Active bool
// commission parameters
Commission
@ -459,13 +459,13 @@ func (v *Validator) String() string {
return fmt.Sprintf(`Validator
Address: %s
SlotPubKeys: %s
Unbonding Height: %v
LastEpochInCommittee: %v
Minimum Self Delegation: %v
Maximum Total Delegation: %v
Description: %v
Commission: %v`,
common2.MustAddressToBech32(v.Address), printSlotPubKeys(v.SlotPubKeys),
v.UnbondingHeight,
v.LastEpochInCommittee,
v.MinSelfDelegation, v.MaxTotalDelegation, v.Description, v.Commission,
)
}

@ -12,7 +12,7 @@ func CreateNewValidator() Validator {
c := Commission{cr, big.NewInt(300)}
d := Description{Name: "SuperHero", Identity: "YouWillNotKnow", Website: "under_construction", Details: "N/A"}
v := Validator{Address: common.Address{}, SlotPubKeys: nil,
UnbondingHeight: big.NewInt(20), MinSelfDelegation: big.NewInt(7),
LastEpochInCommittee: big.NewInt(20), MinSelfDelegation: big.NewInt(7),
Active: false, Commission: c, Description: d}
return v
}

Loading…
Cancel
Save