github.com/iotexproject/iotex-core@v1.14.1-rc1/action/protocol/poll/governance_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  	"strconv"
    11  	"time"
    12  
    13  	"github.com/iotexproject/go-pkgs/hash"
    14  	"github.com/iotexproject/iotex-address/address"
    15  	"github.com/iotexproject/iotex-election/committee"
    16  	"github.com/iotexproject/iotex-election/db"
    17  	"github.com/pkg/errors"
    18  	"go.uber.org/zap"
    19  
    20  	"github.com/iotexproject/iotex-core/action"
    21  	"github.com/iotexproject/iotex-core/action/protocol"
    22  	"github.com/iotexproject/iotex-core/action/protocol/rolldpos"
    23  	"github.com/iotexproject/iotex-core/pkg/log"
    24  	"github.com/iotexproject/iotex-core/state"
    25  )
    26  
    27  type governanceChainCommitteeProtocol struct {
    28  	getBlockTime              GetBlockTime
    29  	electionCommittee         committee.Committee
    30  	initGravityChainHeight    uint64
    31  	addr                      address.Address
    32  	initialCandidatesInterval time.Duration
    33  	sh                        *Slasher
    34  	indexer                   *CandidateIndexer
    35  }
    36  
    37  // NewGovernanceChainCommitteeProtocol creates a Poll Protocol which fetch result from governance chain
    38  func NewGovernanceChainCommitteeProtocol(
    39  	candidatesIndexer *CandidateIndexer,
    40  	electionCommittee committee.Committee,
    41  	initGravityChainHeight uint64,
    42  	getBlockTime GetBlockTime,
    43  	initialCandidatesInterval time.Duration,
    44  	sh *Slasher,
    45  ) (Protocol, error) {
    46  	if electionCommittee == nil {
    47  		return nil, ErrNoElectionCommittee
    48  	}
    49  	if getBlockTime == nil {
    50  		return nil, errors.New("getBlockTime api is not provided")
    51  	}
    52  
    53  	h := hash.Hash160b([]byte(_protocolID))
    54  	addr, err := address.FromBytes(h[:])
    55  	if err != nil {
    56  		log.L().Panic("Error when constructing the address of poll protocol", zap.Error(err))
    57  	}
    58  
    59  	return &governanceChainCommitteeProtocol{
    60  		electionCommittee:         electionCommittee,
    61  		initGravityChainHeight:    initGravityChainHeight,
    62  		getBlockTime:              getBlockTime,
    63  		addr:                      addr,
    64  		initialCandidatesInterval: initialCandidatesInterval,
    65  		sh:                        sh,
    66  		indexer:                   candidatesIndexer,
    67  	}, nil
    68  }
    69  
    70  func (p *governanceChainCommitteeProtocol) CreateGenesisStates(
    71  	ctx context.Context,
    72  	sm protocol.StateManager,
    73  ) (err error) {
    74  	blkCtx := protocol.MustGetBlockCtx(ctx)
    75  	if blkCtx.BlockHeight != 0 {
    76  		return errors.Errorf("Cannot create genesis state for height %d", blkCtx.BlockHeight)
    77  	}
    78  	log.L().Info("Initialize poll protocol", zap.Uint64("height", p.initGravityChainHeight))
    79  	if err = p.sh.CreateGenesisStates(ctx, sm, p.indexer); err != nil {
    80  		return
    81  	}
    82  	var ds state.CandidateList
    83  	for {
    84  		ds, err = p.candidatesByGravityChainHeight(p.initGravityChainHeight)
    85  		if err == nil || errors.Cause(err) != db.ErrNotExist {
    86  			break
    87  		}
    88  		log.L().Debug("calling committee,waiting for a while", zap.Int64("duration", int64(p.initialCandidatesInterval.Seconds())), zap.String("unit", " seconds"))
    89  		time.Sleep(p.initialCandidatesInterval)
    90  	}
    91  	if err != nil {
    92  		return
    93  	}
    94  	log.L().Info("Validating delegates from gravity chain", zap.Any("delegates", ds))
    95  	if err = validateDelegates(ds); err != nil {
    96  		return
    97  	}
    98  	return setCandidates(ctx, sm, p.indexer, ds, uint64(1))
    99  }
   100  
   101  func (p *governanceChainCommitteeProtocol) CreatePostSystemActions(ctx context.Context, sr protocol.StateReader) ([]action.Envelope, error) {
   102  	return createPostSystemActions(ctx, sr, p)
   103  }
   104  
   105  func (p *governanceChainCommitteeProtocol) CreatePreStates(ctx context.Context, sm protocol.StateManager) error {
   106  	return p.sh.CreatePreStates(ctx, sm, p.indexer)
   107  }
   108  
   109  func (p *governanceChainCommitteeProtocol) Handle(ctx context.Context, act action.Action, sm protocol.StateManager) (*action.Receipt, error) {
   110  	return handle(ctx, act, sm, p.indexer, p.addr.String())
   111  }
   112  
   113  func (p *governanceChainCommitteeProtocol) Validate(ctx context.Context, act action.Action, sr protocol.StateReader) error {
   114  	return validate(ctx, sr, p, act)
   115  }
   116  
   117  func (p *governanceChainCommitteeProtocol) candidatesByGravityChainHeight(height uint64) (state.CandidateList, error) {
   118  	r, err := p.electionCommittee.ResultByHeight(height)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  	l := state.CandidateList{}
   123  	for _, c := range r.Delegates() {
   124  		operatorAddress := string(c.OperatorAddress())
   125  		if _, err := address.FromString(operatorAddress); err != nil {
   126  			log.L().Debug(
   127  				"candidate's operator address is invalid",
   128  				zap.String("operatorAddress", operatorAddress),
   129  				zap.String("name", string(c.Name())),
   130  				zap.Error(err),
   131  			)
   132  			continue
   133  		}
   134  		rewardAddress := string(c.RewardAddress())
   135  		if _, err := address.FromString(rewardAddress); err != nil {
   136  			log.L().Debug(
   137  				"candidate's reward address is invalid",
   138  				zap.String("name", string(c.Name())),
   139  				zap.String("rewardAddress", rewardAddress),
   140  				zap.Error(err),
   141  			)
   142  			continue
   143  		}
   144  		l = append(l, &state.Candidate{
   145  			Address:       operatorAddress,
   146  			Votes:         c.Score(),
   147  			RewardAddress: rewardAddress,
   148  			CanName:       c.Name(),
   149  		})
   150  	}
   151  	return l, nil
   152  }
   153  
   154  func (p *governanceChainCommitteeProtocol) CalculateCandidatesByHeight(ctx context.Context, _ protocol.StateReader, height uint64) (state.CandidateList, error) {
   155  	gravityHeight, err := p.getGravityHeight(ctx, height)
   156  	if err != nil {
   157  		return nil, errors.Wrap(err, "failed to get gravity chain height")
   158  	}
   159  	log.L().Debug(
   160  		"fetch delegates from gravity chain",
   161  		zap.Uint64("gravityChainHeight", gravityHeight),
   162  	)
   163  	return p.candidatesByGravityChainHeight(gravityHeight)
   164  }
   165  
   166  func (p *governanceChainCommitteeProtocol) CalculateUnproductiveDelegates(
   167  	ctx context.Context,
   168  	sr protocol.StateReader,
   169  ) ([]string, error) {
   170  	return p.sh.calculateUnproductiveDelegates(ctx, sr)
   171  }
   172  
   173  func (p *governanceChainCommitteeProtocol) Delegates(ctx context.Context, sr protocol.StateReader) (state.CandidateList, error) {
   174  	delegates, _, err := p.sh.GetActiveBlockProducers(ctx, sr, false)
   175  	return delegates, err
   176  }
   177  
   178  func (p *governanceChainCommitteeProtocol) NextDelegates(ctx context.Context, sr protocol.StateReader) (state.CandidateList, error) {
   179  	nextDelegates, _, err := p.sh.GetActiveBlockProducers(ctx, sr, true)
   180  	return nextDelegates, err
   181  }
   182  
   183  func (p *governanceChainCommitteeProtocol) Candidates(ctx context.Context, sr protocol.StateReader) (state.CandidateList, error) {
   184  	candidates, _, err := p.sh.GetCandidates(ctx, sr, false)
   185  	return candidates, err
   186  }
   187  
   188  func (p *governanceChainCommitteeProtocol) NextCandidates(ctx context.Context, sr protocol.StateReader) (state.CandidateList, error) {
   189  	candidates, _, err := p.sh.GetCandidates(ctx, sr, true)
   190  	return candidates, err
   191  }
   192  
   193  func (p *governanceChainCommitteeProtocol) ReadState(
   194  	ctx context.Context,
   195  	sr protocol.StateReader,
   196  	method []byte,
   197  	args ...[]byte,
   198  ) ([]byte, uint64, error) {
   199  	switch string(method) {
   200  	case "GetGravityChainStartHeight":
   201  		if len(args) != 1 {
   202  			return nil, uint64(0), errors.Errorf("invalid number of arguments %d", len(args))
   203  		}
   204  		nativeHeight, err := strconv.ParseUint(string(args[0]), 10, 64)
   205  		if err != nil {
   206  			return nil, uint64(0), err
   207  		}
   208  		gravityStartheight, err := p.getGravityHeight(ctx, nativeHeight)
   209  		if err != nil {
   210  			return nil, uint64(0), err
   211  		}
   212  		return []byte(strconv.FormatUint(gravityStartheight, 10)), nativeHeight, nil
   213  	default:
   214  		return p.sh.ReadState(ctx, sr, p.indexer, method, args...)
   215  	}
   216  }
   217  
   218  // Register registers the protocol with a unique ID
   219  func (p *governanceChainCommitteeProtocol) Register(r *protocol.Registry) error {
   220  	return r.Register(_protocolID, p)
   221  }
   222  
   223  // ForceRegister registers the protocol with a unique ID and force replacing the previous protocol if it exists
   224  func (p *governanceChainCommitteeProtocol) ForceRegister(r *protocol.Registry) error {
   225  	return r.ForceRegister(_protocolID, p)
   226  }
   227  
   228  // Name returns the name of protocol
   229  func (p *governanceChainCommitteeProtocol) Name() string {
   230  	return _protocolID
   231  }
   232  
   233  func (p *governanceChainCommitteeProtocol) getGravityHeight(ctx context.Context, height uint64) (uint64, error) {
   234  	rp := rolldpos.MustGetProtocol(protocol.MustGetRegistry(ctx))
   235  	epochNumber := rp.GetEpochNum(height)
   236  	epochHeight := rp.GetEpochHeight(epochNumber)
   237  	blkTime, err := p.getBlockTime(epochHeight)
   238  	if err != nil {
   239  		return 0, err
   240  	}
   241  	log.L().Debug(
   242  		"get gravity chain height by time",
   243  		zap.Time("time", blkTime),
   244  	)
   245  	return p.electionCommittee.HeightByTime(blkTime)
   246  }