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 }