github.com/iotexproject/iotex-core@v1.14.1-rc1/action/protocol/poll/protocol.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 poll
     7  
     8  import (
     9  	"context"
    10  	"math/big"
    11  	"time"
    12  
    13  	"github.com/iotexproject/iotex-address/address"
    14  	"github.com/iotexproject/iotex-election/committee"
    15  	"github.com/pkg/errors"
    16  
    17  	"github.com/iotexproject/iotex-core/action/protocol"
    18  	"github.com/iotexproject/iotex-core/action/protocol/execution/evm"
    19  	"github.com/iotexproject/iotex-core/action/protocol/staking"
    20  	"github.com/iotexproject/iotex-core/action/protocol/vote"
    21  	"github.com/iotexproject/iotex-core/blockchain"
    22  	"github.com/iotexproject/iotex-core/blockchain/genesis"
    23  	"github.com/iotexproject/iotex-core/pkg/log"
    24  	"github.com/iotexproject/iotex-core/state"
    25  )
    26  
    27  const (
    28  	_protocolID     = "poll"
    29  	_rollDPoSScheme = "ROLLDPOS"
    30  )
    31  
    32  const (
    33  	_modeLifeLong      = "lifeLong"
    34  	_modeGovernanceMix = "governanceMix" // mix governance with native staking contract
    35  	_modeNative        = "native"        // only use go naitve staking
    36  	_modeNativeMix     = "nativeMix"     // native with backward compatibility for governanceMix before fairbank
    37  	_modeConsortium    = "consortium"
    38  
    39  	_blockMetaPrefix = "BlockMeta."
    40  )
    41  
    42  // ErrInconsistentHeight is an error that result of "readFromStateDB" is not consistent with others
    43  var ErrInconsistentHeight = errors.New("data is inconsistent because the state height has been changed")
    44  
    45  // ErrNoElectionCommittee is an error that the election committee is not specified
    46  var ErrNoElectionCommittee = errors.New("no election committee specified")
    47  
    48  // ErrProposedDelegatesLength is an error that the proposed delegate list length is not right
    49  var ErrProposedDelegatesLength = errors.New("the proposed delegate list length")
    50  
    51  // ErrDelegatesNotAsExpected is an error that the delegates are not as expected
    52  var ErrDelegatesNotAsExpected = errors.New("delegates are not as expected")
    53  
    54  // ErrDelegatesNotExist is an error that the delegates cannot be prepared
    55  var ErrDelegatesNotExist = errors.New("delegates cannot be found")
    56  
    57  type (
    58  	// GetCandidates returns the current candidates
    59  	GetCandidates func(protocol.StateReader, uint64, bool, bool) ([]*state.Candidate, uint64, error)
    60  
    61  	// GetProbationList returns current the ProbationList
    62  	GetProbationList func(protocol.StateReader, bool) (*vote.ProbationList, uint64, error)
    63  
    64  	// GetUnproductiveDelegate returns unproductiveDelegate struct which contains a cache of upd info by epochs
    65  	GetUnproductiveDelegate func(protocol.StateReader) (*vote.UnproductiveDelegate, error)
    66  
    67  	// GetBlockTime defines a function to get block creation time
    68  	GetBlockTime func(uint64) (time.Time, error)
    69  
    70  	// Productivity returns the number of produced blocks per producer
    71  	Productivity func(uint64, uint64) (map[string]uint64, error)
    72  
    73  	// Protocol defines the protocol of handling votes
    74  	Protocol interface {
    75  		protocol.Protocol
    76  		protocol.ActionValidator
    77  		protocol.GenesisStateCreator
    78  		Delegates(context.Context, protocol.StateReader) (state.CandidateList, error)
    79  		NextDelegates(context.Context, protocol.StateReader) (state.CandidateList, error)
    80  		Candidates(context.Context, protocol.StateReader) (state.CandidateList, error)
    81  		NextCandidates(context.Context, protocol.StateReader) (state.CandidateList, error)
    82  		// CalculateCandidatesByHeight calculates candidate and returns candidates by chain height
    83  		// TODO: remove height, and read it from state reader
    84  		CalculateCandidatesByHeight(context.Context, protocol.StateReader, uint64) (state.CandidateList, error)
    85  		// CalculateUnproductiveDelegates calculates unproductive delegate on current epoch
    86  		CalculateUnproductiveDelegates(context.Context, protocol.StateReader) ([]string, error)
    87  	}
    88  )
    89  
    90  // FindProtocol finds the registered protocol from registry
    91  func FindProtocol(registry *protocol.Registry) Protocol {
    92  	if registry == nil {
    93  		return nil
    94  	}
    95  	p, ok := registry.Find(_protocolID)
    96  	if !ok {
    97  		return nil
    98  	}
    99  	pp, ok := p.(Protocol)
   100  	if !ok {
   101  		log.S().Panic("fail to cast poll protocol")
   102  	}
   103  	return pp
   104  }
   105  
   106  // MustGetProtocol return a registered protocol from registry
   107  func MustGetProtocol(registry *protocol.Registry) Protocol {
   108  	if registry == nil {
   109  		log.S().Panic("registry cannot be nil")
   110  	}
   111  	p, ok := registry.Find(_protocolID)
   112  	if !ok {
   113  		log.S().Panic("poll protocol is not registered")
   114  	}
   115  
   116  	pp, ok := p.(Protocol)
   117  	if !ok {
   118  		log.S().Panic("fail to cast poll protocol")
   119  	}
   120  
   121  	return pp
   122  }
   123  
   124  // NewProtocol instantiates a rewarding protocol instance.
   125  func NewProtocol(
   126  	scheme string,
   127  	chainConfig blockchain.Config,
   128  	genesisConfig genesis.Genesis,
   129  	candidateIndexer *CandidateIndexer,
   130  	readContract ReadContract,
   131  	getCandidates GetCandidates,
   132  	getprobationList GetProbationList,
   133  	getUnproductiveDelegate GetUnproductiveDelegate,
   134  	electionCommittee committee.Committee,
   135  	stakingProto *staking.Protocol,
   136  	getBlockTimeFunc GetBlockTime,
   137  	productivity Productivity,
   138  	getBlockHash evm.GetBlockHash,
   139  	getBlockTime evm.GetBlockTime,
   140  ) (Protocol, error) {
   141  	if scheme != _rollDPoSScheme {
   142  		return nil, nil
   143  	}
   144  
   145  	var (
   146  		slasher        *Slasher
   147  		scoreThreshold *big.Int
   148  	)
   149  	switch genesisConfig.PollMode {
   150  	case _modeGovernanceMix, _modeNative, _modeNativeMix:
   151  		var (
   152  			err error
   153  			ok  bool
   154  		)
   155  		slasher, err = NewSlasher(
   156  			productivity,
   157  			getCandidates,
   158  			getprobationList,
   159  			getUnproductiveDelegate,
   160  			candidateIndexer,
   161  			genesisConfig.NumCandidateDelegates,
   162  			genesisConfig.NumDelegates,
   163  			genesisConfig.DardanellesNumSubEpochs,
   164  			genesisConfig.ProductivityThreshold,
   165  			genesisConfig.ProbationEpochPeriod,
   166  			genesisConfig.UnproductiveDelegateMaxCacheSize,
   167  			genesisConfig.ProbationIntensityRate)
   168  		if err != nil {
   169  			return nil, err
   170  		}
   171  		scoreThreshold, ok = new(big.Int).SetString(genesisConfig.ScoreThreshold, 10)
   172  		if !ok {
   173  			return nil, errors.Errorf("failed to parse score threshold %s", genesisConfig.ScoreThreshold)
   174  		}
   175  	}
   176  
   177  	var stakingV1 Protocol
   178  	switch genesisConfig.PollMode {
   179  	case _modeGovernanceMix, _modeNativeMix:
   180  		if !genesisConfig.EnableGravityChainVoting || electionCommittee == nil {
   181  			return nil, errors.New("gravity chain voting is not enabled")
   182  		}
   183  		governance, err := NewGovernanceChainCommitteeProtocol(
   184  			candidateIndexer,
   185  			electionCommittee,
   186  			genesisConfig.GravityChainStartHeight,
   187  			getBlockTimeFunc,
   188  			chainConfig.PollInitialCandidatesInterval,
   189  			slasher,
   190  		)
   191  		if err != nil {
   192  			return nil, err
   193  		}
   194  		stakingV1, err = NewStakingCommittee(
   195  			electionCommittee,
   196  			governance,
   197  			readContract,
   198  			genesisConfig.NativeStakingContractAddress,
   199  			genesisConfig.NativeStakingContractCode,
   200  			scoreThreshold,
   201  		)
   202  		if err != nil {
   203  			return nil, err
   204  		}
   205  	}
   206  
   207  	switch genesisConfig.PollMode {
   208  	case _modeLifeLong:
   209  		delegates := genesisConfig.Delegates
   210  		if uint64(len(delegates)) < genesisConfig.NumDelegates {
   211  			return nil, errors.New("invalid delegate address in genesis block")
   212  		}
   213  		return NewLifeLongDelegatesProtocol(delegates), nil
   214  	case _modeGovernanceMix:
   215  		return stakingV1, nil
   216  	case _modeNativeMix, _modeNative:
   217  		stakingV2, err := newNativeStakingV2(candidateIndexer, slasher, scoreThreshold, stakingProto)
   218  		if err != nil {
   219  			return nil, err
   220  		}
   221  		return NewStakingCommand(stakingV1, stakingV2)
   222  	case _modeConsortium:
   223  		return NewConsortiumCommittee(candidateIndexer, readContract, getBlockHash)
   224  	default:
   225  		return nil, errors.Errorf("unsupported poll mode %s", genesisConfig.PollMode)
   226  	}
   227  }
   228  
   229  // ProtocolAddr returns the address generated from protocol id
   230  func ProtocolAddr() address.Address {
   231  	return protocol.HashStringToAddress(_protocolID)
   232  }