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

     1  // Copyright (c) 2020 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 poll
     7  
     8  import (
     9  	"context"
    10  	"math/big"
    11  	"strconv"
    12  
    13  	"github.com/iotexproject/iotex-election/util"
    14  	"github.com/pkg/errors"
    15  	"go.uber.org/zap"
    16  
    17  	"github.com/iotexproject/iotex-core/action/protocol"
    18  	"github.com/iotexproject/iotex-core/action/protocol/rolldpos"
    19  	"github.com/iotexproject/iotex-core/action/protocol/vote"
    20  	"github.com/iotexproject/iotex-core/blockchain/genesis"
    21  	"github.com/iotexproject/iotex-core/crypto"
    22  	"github.com/iotexproject/iotex-core/pkg/log"
    23  	"github.com/iotexproject/iotex-core/state"
    24  )
    25  
    26  // Slasher is the module to slash candidates
    27  type Slasher struct {
    28  	productivity          Productivity
    29  	getCandidates         GetCandidates
    30  	getProbationList      GetProbationList
    31  	getUnprodDelegate     GetUnproductiveDelegate
    32  	indexer               *CandidateIndexer
    33  	numCandidateDelegates uint64
    34  	numDelegates          uint64
    35  	numOfBlocksByEpoch    uint64
    36  	prodThreshold         uint64
    37  	probationEpochPeriod  uint64
    38  	maxProbationPeriod    uint64
    39  	probationIntensity    uint32
    40  }
    41  
    42  // NewSlasher returns a new Slasher
    43  func NewSlasher(
    44  	productivity Productivity,
    45  	getCandidates GetCandidates,
    46  	getProbationList GetProbationList,
    47  	getUnprodDelegate GetUnproductiveDelegate,
    48  	indexer *CandidateIndexer,
    49  	numCandidateDelegates, numDelegates, dardanellesNumSubEpochs, thres, koPeriod, maxKoPeriod uint64,
    50  	koIntensity uint32,
    51  ) (*Slasher, error) {
    52  	return &Slasher{
    53  		productivity:          productivity,
    54  		getCandidates:         getCandidates,
    55  		getProbationList:      getProbationList,
    56  		getUnprodDelegate:     getUnprodDelegate,
    57  		indexer:               indexer,
    58  		numCandidateDelegates: numCandidateDelegates,
    59  		numDelegates:          numDelegates,
    60  		numOfBlocksByEpoch:    numDelegates * dardanellesNumSubEpochs,
    61  		prodThreshold:         thres,
    62  		probationEpochPeriod:  koPeriod,
    63  		maxProbationPeriod:    maxKoPeriod,
    64  		probationIntensity:    koIntensity,
    65  	}, nil
    66  }
    67  
    68  // CreateGenesisStates creates genesis state for slasher
    69  func (sh *Slasher) CreateGenesisStates(ctx context.Context, sm protocol.StateManager, indexer *CandidateIndexer) error {
    70  	g := genesis.MustExtractGenesisContext(ctx)
    71  	if g.IsEaster(uint64(1)) {
    72  		if err := setNextEpochProbationList(sm,
    73  			indexer,
    74  			uint64(1),
    75  			vote.NewProbationList(sh.probationIntensity)); err != nil {
    76  			return err
    77  		}
    78  	}
    79  	return nil
    80  }
    81  
    82  // CreatePreStates is to setup probation list
    83  func (sh *Slasher) CreatePreStates(ctx context.Context, sm protocol.StateManager, indexer *CandidateIndexer) error {
    84  	blkCtx := protocol.MustGetBlockCtx(ctx)
    85  	featureCtx := protocol.MustGetFeatureCtx(ctx)
    86  	featureWithHeightCtx := protocol.MustGetFeatureWithHeightCtx(ctx)
    87  	rp := rolldpos.MustGetProtocol(protocol.MustGetRegistry(ctx))
    88  	epochNum := rp.GetEpochNum(blkCtx.BlockHeight)
    89  	epochStartHeight := rp.GetEpochHeight(epochNum)
    90  	epochLastHeight := rp.GetEpochLastBlockHeight(epochNum)
    91  	nextEpochStartHeight := rp.GetEpochHeight(epochNum + 1)
    92  	if featureCtx.UpdateBlockMeta {
    93  		if err := sh.updateCurrentBlockMeta(ctx, sm); err != nil {
    94  			return errors.Wrap(err, "faild to update current epoch meta")
    95  		}
    96  	}
    97  	if blkCtx.BlockHeight == epochLastHeight && featureWithHeightCtx.CalculateProbationList(nextEpochStartHeight) {
    98  		// if the block height is the end of epoch and next epoch is after the Easter height, calculate probation list for probation and write into state DB
    99  		unqualifiedList, err := sh.CalculateProbationList(ctx, sm, epochNum+1)
   100  		if err != nil {
   101  			return err
   102  		}
   103  		return setNextEpochProbationList(sm, indexer, nextEpochStartHeight, unqualifiedList)
   104  	}
   105  	if blkCtx.BlockHeight == epochStartHeight && featureWithHeightCtx.CalculateProbationList(epochStartHeight) {
   106  		prevHeight, err := shiftCandidates(sm)
   107  		if err != nil {
   108  			return err
   109  		}
   110  		afterHeight, err := shiftProbationList(sm)
   111  		if err != nil {
   112  			return err
   113  		}
   114  		if prevHeight != afterHeight {
   115  			return errors.Wrap(ErrInconsistentHeight, "shifting candidate height is not same as shifting probation height")
   116  		}
   117  	}
   118  	return nil
   119  }
   120  
   121  // ReadState defines slasher's read methods.
   122  func (sh *Slasher) ReadState(
   123  	ctx context.Context,
   124  	sr protocol.StateReader,
   125  	indexer *CandidateIndexer,
   126  	method []byte,
   127  	args ...[]byte,
   128  ) ([]byte, uint64, error) {
   129  	rp := rolldpos.MustGetProtocol(protocol.MustGetRegistry(ctx))
   130  	targetHeight, err := sr.Height()
   131  	if err != nil {
   132  		return nil, uint64(0), err
   133  	}
   134  	epochNum := rp.GetEpochNum(targetHeight)
   135  	epochStartHeight := rp.GetEpochHeight(epochNum)
   136  	if len(args) != 0 {
   137  		epochNumArg, err := strconv.ParseUint(string(args[0]), 10, 64)
   138  		if err != nil {
   139  			return nil, uint64(0), err
   140  		}
   141  		if indexer == nil {
   142  			// consistency check between sr.height and epochNumArg in case of using state reader(not indexer)
   143  			if epochNum != epochNumArg {
   144  				return nil, uint64(0), errors.New("Slasher ReadState arg epochNumber should be same as state reader height, need to set argument/height consistently")
   145  			}
   146  		}
   147  		epochStartHeight = rp.GetEpochHeight(epochNumArg)
   148  	}
   149  	switch string(method) {
   150  	case "CandidatesByEpoch":
   151  		if indexer != nil {
   152  			candidates, err := sh.GetCandidatesFromIndexer(ctx, epochStartHeight)
   153  			if err == nil {
   154  				data, err := candidates.Serialize()
   155  				if err != nil {
   156  					return nil, uint64(0), err
   157  				}
   158  				return data, epochStartHeight, nil
   159  			}
   160  			if err != nil && errors.Cause(err) != ErrIndexerNotExist {
   161  				return nil, uint64(0), err
   162  			}
   163  		}
   164  		candidates, height, err := sh.GetCandidates(ctx, sr, false)
   165  		if err != nil {
   166  			return nil, uint64(0), err
   167  		}
   168  		data, err := candidates.Serialize()
   169  		if err != nil {
   170  			return nil, uint64(0), err
   171  		}
   172  		return data, height, nil
   173  	case "BlockProducersByEpoch":
   174  		if indexer != nil {
   175  			blockProducers, err := sh.GetBPFromIndexer(ctx, epochStartHeight)
   176  			if err == nil {
   177  				data, err := blockProducers.Serialize()
   178  				if err != nil {
   179  					return nil, uint64(0), err
   180  				}
   181  				return data, epochStartHeight, nil
   182  			}
   183  			if err != nil && errors.Cause(err) != ErrIndexerNotExist {
   184  				return nil, uint64(0), err
   185  			}
   186  		}
   187  		bp, height, err := sh.GetBlockProducers(ctx, sr, false)
   188  		if err != nil {
   189  			return nil, uint64(0), err
   190  		}
   191  		data, err := bp.Serialize()
   192  		if err != nil {
   193  			return nil, uint64(0), err
   194  		}
   195  		return data, height, nil
   196  	case "ActiveBlockProducersByEpoch":
   197  		if indexer != nil {
   198  			activeBlockProducers, err := sh.GetABPFromIndexer(ctx, epochStartHeight)
   199  			if err == nil {
   200  				data, err := activeBlockProducers.Serialize()
   201  				if err != nil {
   202  					return nil, uint64(0), err
   203  				}
   204  				return data, epochStartHeight, nil
   205  			}
   206  			if err != nil && errors.Cause(err) != ErrIndexerNotExist {
   207  				return nil, uint64(0), err
   208  			}
   209  		}
   210  		abp, height, err := sh.GetActiveBlockProducers(ctx, sr, false)
   211  		if err != nil {
   212  			return nil, uint64(0), err
   213  		}
   214  		data, err := abp.Serialize()
   215  		if err != nil {
   216  			return nil, uint64(0), err
   217  		}
   218  		return data, height, nil
   219  	case "ProbationListByEpoch":
   220  		if indexer != nil {
   221  			probationList, err := indexer.ProbationList(epochStartHeight)
   222  			if err == nil {
   223  				data, err := probationList.Serialize()
   224  				if err != nil {
   225  					return nil, uint64(0), err
   226  				}
   227  				return data, epochStartHeight, nil
   228  			}
   229  			if err != nil && errors.Cause(err) != ErrIndexerNotExist {
   230  				return nil, uint64(0), err
   231  			}
   232  		}
   233  		probationList, height, err := sh.GetProbationList(ctx, sr, false)
   234  		if err != nil {
   235  			return nil, uint64(0), err
   236  		}
   237  		data, err := probationList.Serialize()
   238  		if err != nil {
   239  			return nil, uint64(0), err
   240  		}
   241  		return data, height, nil
   242  	default:
   243  		return nil, uint64(0), errors.New("corresponding method isn't found")
   244  	}
   245  }
   246  
   247  // GetCandidates returns filtered candidate list
   248  func (sh *Slasher) GetCandidates(ctx context.Context, sr protocol.StateReader, readFromNext bool) (state.CandidateList, uint64, error) {
   249  	rp := rolldpos.MustGetProtocol(protocol.MustGetRegistry(ctx))
   250  	featureWithHeightCtx := protocol.MustGetFeatureWithHeightCtx(ctx)
   251  	targetHeight, err := sr.Height()
   252  	if err != nil {
   253  		return nil, uint64(0), err
   254  	}
   255  	// make sure it's epochStartHeight
   256  	targetEpochStartHeight := rp.GetEpochHeight(rp.GetEpochNum(targetHeight))
   257  	if readFromNext {
   258  		targetEpochNum := rp.GetEpochNum(targetEpochStartHeight) + 1
   259  		targetEpochStartHeight = rp.GetEpochHeight(targetEpochNum) // next epoch start height
   260  	}
   261  	calculate := !featureWithHeightCtx.CalculateProbationList(targetEpochStartHeight)
   262  	candidates, stateHeight, err := sh.getCandidates(sr, targetEpochStartHeight, calculate, readFromNext)
   263  	if err != nil {
   264  		return nil, uint64(0), errors.Wrapf(err, "failed to get candidates at height %d", targetEpochStartHeight)
   265  	}
   266  	// to catch the corner case that since the new block is committed, shift occurs in the middle of processing the request
   267  	if rp.GetEpochNum(targetEpochStartHeight) < rp.GetEpochNum(stateHeight) {
   268  		return nil, uint64(0), errors.Wrap(ErrInconsistentHeight, "state factory epoch number became larger than target epoch number")
   269  	}
   270  	if calculate {
   271  		return candidates, stateHeight, nil
   272  	}
   273  	// After Easter height, probation unqualified delegates based on productivity
   274  	unqualifiedList, _, err := sh.GetProbationList(ctx, sr, readFromNext)
   275  	if err != nil {
   276  		return nil, uint64(0), errors.Wrapf(err, "failed to get probation list at height %d", targetEpochStartHeight)
   277  	}
   278  	// recalculate the voting power for probationlist delegates
   279  	filteredCandidate, err := filterCandidates(candidates, unqualifiedList, targetEpochStartHeight)
   280  	if err != nil {
   281  		return nil, uint64(0), err
   282  	}
   283  	return filteredCandidate, stateHeight, nil
   284  }
   285  
   286  // GetBlockProducers returns BP list
   287  func (sh *Slasher) GetBlockProducers(ctx context.Context, sr protocol.StateReader, readFromNext bool) (state.CandidateList, uint64, error) {
   288  	candidates, height, err := sh.GetCandidates(ctx, sr, readFromNext)
   289  	if err != nil {
   290  		return nil, uint64(0), err
   291  	}
   292  	bp, err := sh.calculateBlockProducer(candidates)
   293  	if err != nil {
   294  		return nil, uint64(0), err
   295  	}
   296  	return bp, height, nil
   297  }
   298  
   299  // GetActiveBlockProducers returns active BP list
   300  func (sh *Slasher) GetActiveBlockProducers(ctx context.Context, sr protocol.StateReader, readFromNext bool) (state.CandidateList, uint64, error) {
   301  	rp := rolldpos.MustGetProtocol(protocol.MustGetRegistry(ctx))
   302  	targetHeight, err := sr.Height()
   303  	if err != nil {
   304  		return nil, uint64(0), err
   305  	}
   306  	// make sure it's epochStartHeight
   307  	targetEpochStartHeight := rp.GetEpochHeight(rp.GetEpochNum(targetHeight))
   308  	if readFromNext {
   309  		targetEpochNum := rp.GetEpochNum(targetEpochStartHeight) + 1
   310  		targetEpochStartHeight = rp.GetEpochHeight(targetEpochNum) // next epoch start height
   311  	}
   312  	blockProducers, height, err := sh.GetBlockProducers(ctx, sr, readFromNext)
   313  	if err != nil {
   314  		return nil, uint64(0), errors.Wrapf(err, "failed to read block producers at height %d", targetEpochStartHeight)
   315  	}
   316  	abp, err := sh.calculateActiveBlockProducer(ctx, blockProducers, targetEpochStartHeight)
   317  	if err != nil {
   318  		return nil, uint64(0), err
   319  	}
   320  	return abp, height, nil
   321  }
   322  
   323  // GetCandidatesFromIndexer returns candidate list from indexer
   324  func (sh *Slasher) GetCandidatesFromIndexer(ctx context.Context, epochStartHeight uint64) (state.CandidateList, error) {
   325  	featureWithHeightCtx := protocol.MustGetFeatureWithHeightCtx(ctx)
   326  	candidates, err := sh.indexer.CandidateList(epochStartHeight)
   327  	if err != nil {
   328  		return nil, err
   329  	}
   330  	if !featureWithHeightCtx.CalculateProbationList(epochStartHeight) {
   331  		return candidates, nil
   332  	}
   333  	// After Easter height, probation unqualified delegates based on productivity
   334  	probationList, err := sh.indexer.ProbationList(epochStartHeight)
   335  	if err != nil {
   336  		return nil, err
   337  	}
   338  	// recalculate the voting power for probationlist delegates
   339  	return filterCandidates(candidates, probationList, epochStartHeight)
   340  }
   341  
   342  // GetBPFromIndexer returns BP list from indexer
   343  func (sh *Slasher) GetBPFromIndexer(ctx context.Context, epochStartHeight uint64) (state.CandidateList, error) {
   344  	candidates, err := sh.GetCandidatesFromIndexer(ctx, epochStartHeight)
   345  	if err != nil {
   346  		return nil, err
   347  	}
   348  	return sh.calculateBlockProducer(candidates)
   349  }
   350  
   351  // GetABPFromIndexer returns active BP list from indexer
   352  func (sh *Slasher) GetABPFromIndexer(ctx context.Context, epochStartHeight uint64) (state.CandidateList, error) {
   353  	blockProducers, err := sh.GetBPFromIndexer(ctx, epochStartHeight)
   354  	if err != nil {
   355  		return nil, err
   356  	}
   357  	return sh.calculateActiveBlockProducer(ctx, blockProducers, epochStartHeight)
   358  }
   359  
   360  // GetProbationList returns the probation list at given epoch
   361  func (sh *Slasher) GetProbationList(ctx context.Context, sr protocol.StateReader, readFromNext bool) (*vote.ProbationList, uint64, error) {
   362  	rp := rolldpos.MustGetProtocol(protocol.MustGetRegistry(ctx))
   363  	featureWithHeightCtx := protocol.MustGetFeatureWithHeightCtx(ctx)
   364  	targetHeight, err := sr.Height()
   365  	if err != nil {
   366  		return nil, uint64(0), err
   367  	}
   368  	// make sure it's epochStartHeight
   369  	targetEpochStartHeight := rp.GetEpochHeight(rp.GetEpochHeight(targetHeight))
   370  	if readFromNext {
   371  		targetEpochNum := rp.GetEpochNum(targetEpochStartHeight) + 1
   372  		targetEpochStartHeight = rp.GetEpochHeight(targetEpochNum) // next epoch start height
   373  	}
   374  	if !featureWithHeightCtx.CalculateProbationList(targetEpochStartHeight) {
   375  		return nil, uint64(0), errors.New("Before Easter, there is no probation list in stateDB")
   376  	}
   377  	unqualifiedList, stateHeight, err := sh.getProbationList(sr, readFromNext)
   378  	if err != nil {
   379  		return nil, uint64(0), err
   380  	}
   381  	// to catch the corner case that since the new block is committed, shift occurs in the middle of processing the request
   382  	if rp.GetEpochNum(targetEpochStartHeight) < rp.GetEpochNum(stateHeight) {
   383  		return nil, uint64(0), errors.Wrap(ErrInconsistentHeight, "state factory tip epoch number became larger than target epoch number")
   384  	}
   385  	return unqualifiedList, stateHeight, nil
   386  }
   387  
   388  // CalculateProbationList calculates probation list according to productivity
   389  func (sh *Slasher) CalculateProbationList(
   390  	ctx context.Context,
   391  	sm protocol.StateManager,
   392  	epochNum uint64,
   393  ) (*vote.ProbationList, error) {
   394  	rp := rolldpos.MustGetProtocol(protocol.MustGetRegistry(ctx))
   395  	g := genesis.MustExtractGenesisContext(ctx)
   396  	easterEpochNum := rp.GetEpochNum(g.EasterBlockHeight)
   397  
   398  	nextProbationlist := &vote.ProbationList{
   399  		IntensityRate: sh.probationIntensity,
   400  	}
   401  	upd, err := sh.getUnprodDelegate(sm)
   402  	if err != nil {
   403  		if errors.Cause(err) == state.ErrStateNotExist {
   404  			if upd, err = vote.NewUnproductiveDelegate(sh.probationEpochPeriod, sh.maxProbationPeriod); err != nil {
   405  				return nil, errors.Wrap(err, "failed to make new upd")
   406  			}
   407  		} else {
   408  			return nil, errors.Wrapf(err, "failed to read upd struct from state DB at epoch number %d", epochNum)
   409  		}
   410  	}
   411  	unqualifiedDelegates := make(map[string]uint32)
   412  	if epochNum <= easterEpochNum+sh.probationEpochPeriod {
   413  		// if epoch number is smaller than easterEpochNum+K(probation period), calculate it one-by-one (initialize).
   414  		log.L().Debug("Before using probation list",
   415  			zap.Uint64("epochNum", epochNum),
   416  			zap.Uint64("easterEpochNum", easterEpochNum),
   417  			zap.Uint64("probationEpochPeriod", sh.probationEpochPeriod),
   418  		)
   419  		existinglist := upd.DelegateList()
   420  		for _, listByEpoch := range existinglist {
   421  			for _, addr := range listByEpoch {
   422  				if _, ok := unqualifiedDelegates[addr]; !ok {
   423  					unqualifiedDelegates[addr] = 1
   424  				} else {
   425  					unqualifiedDelegates[addr]++
   426  				}
   427  			}
   428  		}
   429  		// calculate upd of epochNum-1 (latest)
   430  		uq, err := sh.calculateUnproductiveDelegates(ctx, sm)
   431  		if err != nil {
   432  			return nil, errors.Wrapf(err, "failed to calculate current epoch upd %d", epochNum-1)
   433  		}
   434  		for _, addr := range uq {
   435  			if _, ok := unqualifiedDelegates[addr]; !ok {
   436  				unqualifiedDelegates[addr] = 1
   437  			} else {
   438  				unqualifiedDelegates[addr]++
   439  			}
   440  		}
   441  		if err := upd.AddRecentUPD(uq); err != nil {
   442  			return nil, errors.Wrap(err, "failed to add recent upd")
   443  		}
   444  		nextProbationlist.ProbationInfo = unqualifiedDelegates
   445  		return nextProbationlist, setUnproductiveDelegates(sm, upd)
   446  	}
   447  	// ProbationList[N] = ProbationList[N-1] - Low-productivity-list[N-K-1] + Low-productivity-list[N-1]
   448  	log.L().Debug("Using probationList",
   449  		zap.Uint64("epochNum", epochNum),
   450  		zap.Uint64("easterEpochNum", easterEpochNum),
   451  		zap.Uint64("probationEpochPeriod", sh.probationEpochPeriod),
   452  	)
   453  	prevProbationlist, _, err := sh.getProbationList(sm, false)
   454  	if err != nil {
   455  		return nil, errors.Wrap(err, "failed to read latest probation list")
   456  	}
   457  	probationMap := prevProbationlist.ProbationInfo
   458  	if probationMap == nil {
   459  		probationMap = make(map[string]uint32)
   460  	}
   461  	skipList := upd.ReadOldestUPD()
   462  	for _, addr := range skipList {
   463  		if _, ok := probationMap[addr]; !ok {
   464  			log.L().Fatal("skipping list element doesn't exist among one of existing map")
   465  			continue
   466  		}
   467  		probationMap[addr]--
   468  	}
   469  	addList, err := sh.calculateUnproductiveDelegates(ctx, sm)
   470  	if err != nil {
   471  		return nil, errors.Wrapf(err, "failed to calculate current epoch upd %d", epochNum-1)
   472  	}
   473  	if err := upd.AddRecentUPD(addList); err != nil {
   474  		return nil, errors.Wrap(err, "failed to add recent upd")
   475  	}
   476  	for _, addr := range addList {
   477  		if _, ok := probationMap[addr]; ok {
   478  			probationMap[addr]++
   479  			continue
   480  		}
   481  		probationMap[addr] = 1
   482  	}
   483  
   484  	for addr, count := range probationMap {
   485  		if count == 0 {
   486  			delete(probationMap, addr)
   487  		}
   488  	}
   489  	nextProbationlist.ProbationInfo = probationMap
   490  	return nextProbationlist, setUnproductiveDelegates(sm, upd)
   491  }
   492  
   493  func (sh *Slasher) calculateUnproductiveDelegates(ctx context.Context, sr protocol.StateReader) ([]string, error) {
   494  	blkCtx := protocol.MustGetBlockCtx(ctx)
   495  	bcCtx := protocol.MustGetBlockchainCtx(ctx)
   496  	featureCtx := protocol.MustGetFeatureCtx(ctx)
   497  	rp := rolldpos.MustGetProtocol(protocol.MustGetRegistry(ctx))
   498  	epochNum := rp.GetEpochNum(blkCtx.BlockHeight)
   499  	delegates, _, err := sh.GetActiveBlockProducers(ctx, sr, false)
   500  	if err != nil {
   501  		return nil, err
   502  	}
   503  	productivityFunc := sh.productivity
   504  	if featureCtx.CurrentEpochProductivity {
   505  		productivityFunc = func(start, end uint64) (map[string]uint64, error) {
   506  			return currentEpochProductivity(sr, start, end, sh.numOfBlocksByEpoch)
   507  		}
   508  	}
   509  	numBlks, produce, err := rp.ProductivityByEpoch(
   510  		epochNum,
   511  		bcCtx.Tip.Height,
   512  		productivityFunc,
   513  	)
   514  	if err != nil {
   515  		return nil, err
   516  	}
   517  	// The current block is not included, so add it
   518  	numBlks++
   519  	if _, ok := produce[blkCtx.Producer.String()]; ok {
   520  		produce[blkCtx.Producer.String()]++
   521  	} else {
   522  		produce[blkCtx.Producer.String()] = 1
   523  	}
   524  
   525  	for _, abp := range delegates {
   526  		if _, ok := produce[abp.Address]; !ok {
   527  			produce[abp.Address] = 0
   528  		}
   529  	}
   530  	unqualified := make([]string, 0)
   531  	expectedNumBlks := numBlks / uint64(len(produce))
   532  	for addr, actualNumBlks := range produce {
   533  		if actualNumBlks*100/expectedNumBlks < sh.prodThreshold {
   534  			unqualified = append(unqualified, addr)
   535  		}
   536  	}
   537  	return unqualified, nil
   538  }
   539  
   540  func (sh *Slasher) updateCurrentBlockMeta(ctx context.Context, sm protocol.StateManager) error {
   541  	blkCtx := protocol.MustGetBlockCtx(ctx)
   542  	currentBlockMeta := NewBlockMeta(blkCtx.BlockHeight, blkCtx.Producer.String(), blkCtx.BlockTimeStamp)
   543  	return setCurrentBlockMeta(sm, currentBlockMeta, blkCtx.BlockHeight, sh.numOfBlocksByEpoch)
   544  }
   545  
   546  // calculateBlockProducer calculates block producer by given candidate list
   547  func (sh *Slasher) calculateBlockProducer(candidates state.CandidateList) (state.CandidateList, error) {
   548  	var blockProducers state.CandidateList
   549  	for i, candidate := range candidates {
   550  		if uint64(i) >= sh.numCandidateDelegates {
   551  			break
   552  		}
   553  		if candidate.Votes.Cmp(big.NewInt(0)) == 0 {
   554  			// if the voting power is 0, exclude from being a block producer(hard probation)
   555  			continue
   556  		}
   557  		blockProducers = append(blockProducers, candidate)
   558  	}
   559  	return blockProducers, nil
   560  }
   561  
   562  // calculateActiveBlockProducer calculates active block producer by given block producer list
   563  func (sh *Slasher) calculateActiveBlockProducer(
   564  	ctx context.Context,
   565  	blockProducers state.CandidateList,
   566  	epochStartHeight uint64,
   567  ) (state.CandidateList, error) {
   568  	var blockProducerList []string
   569  	blockProducerMap := make(map[string]*state.Candidate)
   570  	for _, bp := range blockProducers {
   571  		blockProducerList = append(blockProducerList, bp.Address)
   572  		blockProducerMap[bp.Address] = bp
   573  	}
   574  	crypto.SortCandidates(blockProducerList, epochStartHeight, crypto.CryptoSeed)
   575  
   576  	length := int(sh.numDelegates)
   577  	if len(blockProducerList) < length {
   578  		// TODO: if the number of delegates is smaller than expected, should it return error or not?
   579  		length = len(blockProducerList)
   580  		log.L().Warn(
   581  			"the number of block producer is less than expected",
   582  			zap.Int("actual block producer", len(blockProducerList)),
   583  			zap.Uint64("expected", sh.numDelegates),
   584  		)
   585  	}
   586  	var activeBlockProducers state.CandidateList
   587  	for i := 0; i < length; i++ {
   588  		activeBlockProducers = append(activeBlockProducers, blockProducerMap[blockProducerList[i]])
   589  	}
   590  	return activeBlockProducers, nil
   591  }
   592  
   593  // filterCandidates returns filtered candidate list by given raw candidate/ probation list
   594  func filterCandidates(
   595  	candidates state.CandidateList,
   596  	unqualifiedList *vote.ProbationList,
   597  	epochStartHeight uint64,
   598  ) (state.CandidateList, error) {
   599  	candidatesMap := make(map[string]*state.Candidate)
   600  	updatedVotingPower := make(map[string]*big.Int)
   601  	intensityRate := float64(uint32(100)-unqualifiedList.IntensityRate) / float64(100)
   602  	for _, cand := range candidates {
   603  		filterCand := cand.Clone()
   604  		if _, ok := unqualifiedList.ProbationInfo[cand.Address]; ok {
   605  			// if it is an unqualified delegate, multiply the voting power with probation intensity rate
   606  			votingPower := new(big.Float).SetInt(filterCand.Votes)
   607  			filterCand.Votes, _ = votingPower.Mul(votingPower, big.NewFloat(intensityRate)).Int(nil)
   608  		}
   609  		updatedVotingPower[filterCand.Address] = filterCand.Votes
   610  		candidatesMap[filterCand.Address] = filterCand
   611  	}
   612  	// sort again with updated voting power
   613  	sorted := util.Sort(updatedVotingPower, epochStartHeight)
   614  	var verifiedCandidates state.CandidateList
   615  	for _, name := range sorted {
   616  		verifiedCandidates = append(verifiedCandidates, candidatesMap[name])
   617  	}
   618  	return verifiedCandidates, nil
   619  }
   620  
   621  // currentEpochProductivity returns the map of the number of blocks produced per delegate of current epoch
   622  func currentEpochProductivity(sr protocol.StateReader, start uint64, end uint64, numOfBlocksByEpoch uint64) (map[string]uint64, error) {
   623  	log.L().Debug("Read current epoch productivity",
   624  		zap.Uint64("start height", start),
   625  		zap.Uint64("end height", end),
   626  	)
   627  	stats := make(map[string]uint64)
   628  	blockmetas, err := allBlockMetasFromDB(sr, numOfBlocksByEpoch)
   629  	if err != nil {
   630  		return nil, err
   631  	}
   632  	expectedCount := end - start + 1
   633  	count := uint64(0)
   634  	for _, blockmeta := range blockmetas {
   635  		if blockmeta.Height < start || blockmeta.Height > end {
   636  			continue
   637  		}
   638  		stats[blockmeta.Producer]++
   639  		count++
   640  	}
   641  	if expectedCount != count {
   642  		log.L().Debug(
   643  			"block metas from stateDB count is not same as expected",
   644  			zap.Uint64("expected", expectedCount),
   645  			zap.Uint64("actual", count),
   646  		)
   647  		return nil, errors.New("block metas from stateDB doesn't have enough data for given start, end height")
   648  	}
   649  	return stats, nil
   650  }