github.com/iotexproject/iotex-core@v1.14.1-rc1/action/protocol/staking/vote_reviser.go (about)

     1  package staking
     2  
     3  import (
     4  	"math/big"
     5  	"sort"
     6  
     7  	"github.com/pkg/errors"
     8  	"go.uber.org/zap"
     9  
    10  	"github.com/iotexproject/iotex-core/blockchain/genesis"
    11  	"github.com/iotexproject/iotex-core/pkg/log"
    12  	"github.com/iotexproject/iotex-core/state"
    13  )
    14  
    15  // VoteReviser is used to recalculate candidate votes.
    16  type VoteReviser struct {
    17  	reviseHeights      []uint64
    18  	cache              map[uint64]CandidateList
    19  	c                  genesis.VoteWeightCalConsts
    20  	correctCandsHeight uint64
    21  }
    22  
    23  // NewVoteReviser creates a VoteReviser.
    24  func NewVoteReviser(c genesis.VoteWeightCalConsts, correctCandsHeight uint64, reviseHeights ...uint64) *VoteReviser {
    25  	return &VoteReviser{
    26  		reviseHeights:      reviseHeights,
    27  		cache:              make(map[uint64]CandidateList),
    28  		c:                  c,
    29  		correctCandsHeight: correctCandsHeight,
    30  	}
    31  }
    32  
    33  // Revise recalculate candidate votes on preset revising height.
    34  func (vr *VoteReviser) Revise(csm CandidateStateManager, height uint64) error {
    35  	if !vr.isCacheExist(height) {
    36  		cands, err := vr.calculateVoteWeight(csm)
    37  		if err != nil {
    38  			return err
    39  		}
    40  		sort.Sort(cands)
    41  		if vr.needCorrectCands(height) {
    42  			cands, err = vr.correctAliasCands(csm, cands)
    43  			if err != nil {
    44  				return err
    45  			}
    46  		}
    47  		vr.storeToCache(height, cands)
    48  	}
    49  	return vr.flush(height, csm)
    50  }
    51  
    52  func (vr *VoteReviser) correctAliasCands(csm CandidateStateManager, cands CandidateList) (CandidateList, error) {
    53  	var retval CandidateList
    54  	for _, c := range csm.DirtyView().candCenter.base.nameMap {
    55  		retval = append(retval, c)
    56  	}
    57  	for _, c := range csm.DirtyView().candCenter.base.operatorMap {
    58  		retval = append(retval, c)
    59  	}
    60  	sort.Sort(retval)
    61  	ownerMap := map[string]*Candidate{}
    62  	for _, cand := range csm.DirtyView().candCenter.base.owners {
    63  		ownerMap[cand.Owner.String()] = cand
    64  	}
    65  	for _, c := range cands {
    66  		if owner, ok := ownerMap[c.Owner.String()]; ok {
    67  			c.Operator = owner.Operator
    68  			c.Reward = owner.Reward
    69  			c.Name = owner.Name
    70  		}
    71  		retval = append(retval, c)
    72  	}
    73  	return retval, nil
    74  }
    75  
    76  func (vr *VoteReviser) result(height uint64) (CandidateList, bool) {
    77  	cands, ok := vr.cache[height]
    78  	if !ok {
    79  		return nil, false
    80  	}
    81  	return cands, true
    82  }
    83  
    84  func (vr *VoteReviser) storeToCache(height uint64, cands CandidateList) {
    85  	vr.cache[height] = cands
    86  }
    87  
    88  func (vr *VoteReviser) isCacheExist(height uint64) bool {
    89  	_, ok := vr.cache[height]
    90  	return ok
    91  }
    92  
    93  // NeedRevise returns true if height needs revise
    94  func (vr *VoteReviser) NeedRevise(height uint64) bool {
    95  	for _, h := range vr.reviseHeights {
    96  		if height == h {
    97  			return true
    98  		}
    99  	}
   100  	return vr.needCorrectCands(height)
   101  }
   102  
   103  // NeedCorrectCands returns true if height needs to correct candidates
   104  func (vr *VoteReviser) needCorrectCands(height uint64) bool {
   105  	return height == vr.correctCandsHeight
   106  }
   107  
   108  func (vr *VoteReviser) calculateVoteWeight(csm CandidateStateManager) (CandidateList, error) {
   109  	csr := newCandidateStateReader(csm.SM())
   110  	cands, _, err := csr.getAllCandidates()
   111  	switch {
   112  	case errors.Cause(err) == state.ErrStateNotExist:
   113  	case err != nil:
   114  		return nil, err
   115  	}
   116  	candm := make(map[string]*Candidate)
   117  	for _, cand := range cands {
   118  		candm[cand.Owner.String()] = cand.Clone()
   119  		candm[cand.Owner.String()].Votes = new(big.Int)
   120  		candm[cand.Owner.String()].SelfStake = new(big.Int)
   121  	}
   122  	buckets, _, err := csr.getAllBuckets()
   123  	switch {
   124  	case errors.Cause(err) == state.ErrStateNotExist:
   125  	case err != nil:
   126  		return nil, err
   127  	}
   128  
   129  	for _, bucket := range buckets {
   130  		if bucket.isUnstaked() {
   131  			continue
   132  		}
   133  		cand, ok := candm[bucket.Candidate.String()]
   134  		if !ok {
   135  			log.L().Error("invalid bucket candidate", zap.Uint64("bucket index", bucket.Index), zap.String("candidate", bucket.Candidate.String()))
   136  			continue
   137  		}
   138  
   139  		if cand.SelfStakeBucketIdx == bucket.Index {
   140  			if err = cand.AddVote(CalculateVoteWeight(vr.c, bucket, true)); err != nil {
   141  				log.L().Error("failed to add vote for candidate",
   142  					zap.Uint64("bucket index", bucket.Index),
   143  					zap.String("candidate", bucket.Candidate.String()),
   144  					zap.Error(err))
   145  				continue
   146  			}
   147  			cand.SelfStake = bucket.StakedAmount
   148  		} else {
   149  			_ = cand.AddVote(CalculateVoteWeight(vr.c, bucket, false))
   150  		}
   151  	}
   152  
   153  	cands = make(CandidateList, 0, len(candm))
   154  	for _, cand := range candm {
   155  		cands = append(cands, cand)
   156  	}
   157  	return cands, nil
   158  }
   159  
   160  func (vr *VoteReviser) flush(height uint64, csm CandidateStateManager) error {
   161  	cands, ok := vr.cache[height]
   162  	if !ok {
   163  		return nil
   164  	}
   165  	log.L().Info("committed revise action",
   166  		zap.Uint64("height", height), zap.Int("number of cands", len(cands)))
   167  	for _, cand := range cands {
   168  		if err := csm.Upsert(cand); err != nil {
   169  			return err
   170  		}
   171  		log.L().Info("committed revise action",
   172  			zap.String("name", cand.Name), zap.String("votes", cand.Votes.String()))
   173  	}
   174  	return nil
   175  }