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

     1  // Copyright (c) 2019 IoTeX Foundation
     2  // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
     3  // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
     4  // This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
     5  
     6  package candidatesutil
     7  
     8  import (
     9  	"math/big"
    10  	"sort"
    11  
    12  	"go.uber.org/zap"
    13  
    14  	"github.com/pkg/errors"
    15  
    16  	"github.com/iotexproject/go-pkgs/hash"
    17  	"github.com/iotexproject/iotex-address/address"
    18  	"github.com/iotexproject/iotex-core/action/protocol"
    19  	"github.com/iotexproject/iotex-core/action/protocol/vote"
    20  	"github.com/iotexproject/iotex-core/pkg/log"
    21  	"github.com/iotexproject/iotex-core/pkg/util/byteutil"
    22  	"github.com/iotexproject/iotex-core/state"
    23  )
    24  
    25  // CandidatesPrefix is the prefix of the key of candidateList
    26  const CandidatesPrefix = "Candidates."
    27  
    28  // CurCandidateKey is the key of current candidate list
    29  const CurCandidateKey = "CurrentCandidateList."
    30  
    31  // NxtCandidateKey is the key of next candidate list
    32  const NxtCandidateKey = "NextCandidateList."
    33  
    34  // CurProbationKey is the key of current probation list
    35  const CurProbationKey = "CurrentKickoutKey."
    36  
    37  // NxtProbationKey is the key of next probation list
    38  const NxtProbationKey = "NextKickoutKey."
    39  
    40  // UnproductiveDelegateKey is the key of unproductive Delegate struct
    41  const UnproductiveDelegateKey = "UnproductiveDelegateKey."
    42  
    43  // CandidatesFromDB returns array of Candidates in candidate pool of a given height or current epoch
    44  func CandidatesFromDB(sr protocol.StateReader, height uint64, loadCandidatesLegacy bool, epochStartPoint bool) ([]*state.Candidate, uint64, error) {
    45  	var candidates state.CandidateList
    46  	var stateHeight uint64
    47  	var err error
    48  	if loadCandidatesLegacy {
    49  		// Load Candidates on the given height from underlying db [deprecated]
    50  		candidatesKey := ConstructLegacyKey(height)
    51  		stateHeight, err = sr.State(&candidates, protocol.LegacyKeyOption(candidatesKey))
    52  	} else {
    53  		candidatesKey := ConstructKey(CurCandidateKey)
    54  		if epochStartPoint {
    55  			// if not shifted yet
    56  			log.L().Debug("Read candidate list with next candidate key", zap.Uint64("height", height))
    57  			candidatesKey = ConstructKey(NxtCandidateKey)
    58  		}
    59  		stateHeight, err = sr.State(
    60  			&candidates,
    61  			protocol.KeyOption(candidatesKey[:]),
    62  			protocol.NamespaceOption(protocol.SystemNamespace),
    63  		)
    64  	}
    65  	log.L().Debug(
    66  		"CandidatesFromDB",
    67  		zap.Bool("loadCandidatesLegacy", loadCandidatesLegacy),
    68  		zap.Uint64("height", height),
    69  		zap.Uint64("stateHeight", stateHeight),
    70  		zap.Any("candidates", candidates),
    71  		zap.Error(err),
    72  	)
    73  	if errors.Cause(err) == nil {
    74  		if len(candidates) > 0 {
    75  			return candidates, stateHeight, nil
    76  		}
    77  		err = state.ErrStateNotExist
    78  	}
    79  	return nil, stateHeight, errors.Wrapf(
    80  		err,
    81  		"failed to get candidates with epochStartPoint: %t",
    82  		epochStartPoint,
    83  	)
    84  }
    85  
    86  // ProbationListFromDB returns array of probation list at current epoch
    87  func ProbationListFromDB(sr protocol.StateReader, epochStartPoint bool) (*vote.ProbationList, uint64, error) {
    88  	probationList := &vote.ProbationList{}
    89  	probationlistKey := ConstructKey(CurProbationKey)
    90  	if epochStartPoint {
    91  		// if not shifted yet
    92  		log.L().Debug("Read probation list with next probation key")
    93  		probationlistKey = ConstructKey(NxtProbationKey)
    94  	}
    95  	stateHeight, err := sr.State(
    96  		probationList,
    97  		protocol.KeyOption(probationlistKey[:]),
    98  		protocol.NamespaceOption(protocol.SystemNamespace),
    99  	)
   100  	log.L().Debug(
   101  		"GetProbationList",
   102  		zap.Any("Probation list", probationList.ProbationInfo),
   103  		zap.Uint64("state height", stateHeight),
   104  		zap.Error(err),
   105  	)
   106  	if err == nil {
   107  		return probationList, stateHeight, nil
   108  	}
   109  	return nil, stateHeight, errors.Wrapf(
   110  		err,
   111  		"failed to get probation list with epochStartPoint: %t",
   112  		epochStartPoint,
   113  	)
   114  }
   115  
   116  // UnproductiveDelegateFromDB returns latest UnproductiveDelegate struct
   117  func UnproductiveDelegateFromDB(sr protocol.StateReader) (*vote.UnproductiveDelegate, error) {
   118  	upd := &vote.UnproductiveDelegate{}
   119  	updKey := ConstructKey(UnproductiveDelegateKey)
   120  	stateHeight, err := sr.State(
   121  		upd,
   122  		protocol.KeyOption(updKey[:]),
   123  		protocol.NamespaceOption(protocol.SystemNamespace),
   124  	)
   125  	log.L().Debug(
   126  		"GetUnproductiveDelegate",
   127  		zap.Uint64("state height", stateHeight),
   128  		zap.Error(err),
   129  	)
   130  	if err == nil {
   131  		return upd, nil
   132  	}
   133  	return nil, err
   134  }
   135  
   136  // ConstructLegacyKey constructs a key for candidates storage (deprecated version)
   137  func ConstructLegacyKey(height uint64) hash.Hash160 {
   138  	heightInBytes := byteutil.Uint64ToBytes(height)
   139  	k := []byte(CandidatesPrefix)
   140  	k = append(k, heightInBytes...)
   141  	return hash.Hash160b(k)
   142  }
   143  
   144  // ConstructKey constructs a const key
   145  func ConstructKey(key string) hash.Hash256 {
   146  	bytesKey := []byte(key)
   147  	return hash.Hash256b(bytesKey)
   148  }
   149  
   150  // LoadAndAddCandidates loads candidates from trie and adds a new candidate
   151  func LoadAndAddCandidates(sm protocol.StateManager, blkHeight uint64, addr string) error {
   152  	candidateMap, err := GetMostRecentCandidateMap(sm, blkHeight)
   153  	if err != nil {
   154  		return errors.Wrap(err, "failed to get most recent candidates from trie")
   155  	}
   156  	if err := addCandidate(candidateMap, addr); err != nil {
   157  		return errors.Wrap(err, "failed to add candidate to candidate map")
   158  	}
   159  	return storeCandidates(candidateMap, sm, blkHeight)
   160  }
   161  
   162  // GetMostRecentCandidateMap gets the most recent candidateMap from trie
   163  func GetMostRecentCandidateMap(sm protocol.StateManager, blkHeight uint64) (map[hash.Hash160]*state.Candidate, error) {
   164  	var sc state.CandidateList
   165  	for h := int(blkHeight); h >= 0; h-- {
   166  		candidatesKey := ConstructLegacyKey(uint64(h))
   167  		var err error
   168  		if _, err = sm.State(&sc, protocol.LegacyKeyOption(candidatesKey)); err == nil {
   169  			return state.CandidatesToMap(sc)
   170  		}
   171  		if errors.Cause(err) != state.ErrStateNotExist {
   172  			return nil, errors.Wrap(err, "failed to get most recent state of candidateList")
   173  		}
   174  	}
   175  	if blkHeight == uint64(0) || blkHeight == uint64(1) {
   176  		return make(map[hash.Hash160]*state.Candidate), nil
   177  	}
   178  	return nil, errors.Wrap(state.ErrStateNotExist, "failed to get most recent state of candidateList")
   179  }
   180  
   181  // addCandidate adds a new candidate to candidateMap
   182  func addCandidate(candidateMap map[hash.Hash160]*state.Candidate, encodedAddr string) error {
   183  	addr, err := address.FromString(encodedAddr)
   184  	if err != nil {
   185  		return errors.Wrap(err, "failed to get public key hash from account address")
   186  	}
   187  	addrHash := hash.BytesToHash160(addr.Bytes())
   188  	if _, ok := candidateMap[addrHash]; !ok {
   189  		candidateMap[addrHash] = &state.Candidate{
   190  			Address: encodedAddr,
   191  			Votes:   big.NewInt(0),
   192  		}
   193  	}
   194  	return nil
   195  }
   196  
   197  // storeCandidates puts updated candidates to trie
   198  func storeCandidates(candidateMap map[hash.Hash160]*state.Candidate, sm protocol.StateManager, blkHeight uint64) error {
   199  	candidateList, err := state.MapToCandidates(candidateMap)
   200  	if err != nil {
   201  		return errors.Wrap(err, "failed to convert candidate map to candidate list")
   202  	}
   203  	sort.Sort(candidateList)
   204  	candidatesKey := ConstructLegacyKey(blkHeight)
   205  	_, err = sm.PutState(&candidateList, protocol.LegacyKeyOption(candidatesKey))
   206  	return err
   207  }