github.com/iotexproject/iotex-core@v1.14.1-rc1/consensus/scheme/rolldpos/rolldposctx.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 rolldpos
     7  
     8  import (
     9  	"context"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/facebookgo/clock"
    14  	fsm "github.com/iotexproject/go-fsm"
    15  	"github.com/iotexproject/go-pkgs/crypto"
    16  	"github.com/pkg/errors"
    17  	"github.com/prometheus/client_golang/prometheus"
    18  	"go.uber.org/zap"
    19  
    20  	"github.com/iotexproject/iotex-core/action/protocol/rolldpos"
    21  	"github.com/iotexproject/iotex-core/blockchain"
    22  	"github.com/iotexproject/iotex-core/blockchain/block"
    23  	"github.com/iotexproject/iotex-core/consensus/consensusfsm"
    24  	"github.com/iotexproject/iotex-core/consensus/scheme"
    25  	"github.com/iotexproject/iotex-core/db"
    26  	"github.com/iotexproject/iotex-core/endorsement"
    27  	"github.com/iotexproject/iotex-core/pkg/log"
    28  )
    29  
    30  var (
    31  	_timeSlotMtc = prometheus.NewGaugeVec(
    32  		prometheus.GaugeOpts{
    33  			Name: "iotex_consensus_round",
    34  			Help: "Consensus round",
    35  		},
    36  		[]string{},
    37  	)
    38  
    39  	_blockIntervalMtc = prometheus.NewGaugeVec(
    40  		prometheus.GaugeOpts{
    41  			Name: "iotex_consensus_block_interval",
    42  			Help: "Consensus block interval",
    43  		},
    44  		[]string{},
    45  	)
    46  
    47  	_consensusDurationMtc = prometheus.NewGaugeVec(
    48  		prometheus.GaugeOpts{
    49  			Name: "iotex_consensus_elapse_time",
    50  			Help: "Consensus elapse time.",
    51  		},
    52  		[]string{},
    53  	)
    54  
    55  	_consensusHeightMtc = prometheus.NewGaugeVec(
    56  		prometheus.GaugeOpts{
    57  			Name: "iotex_consensus_height",
    58  			Help: "Consensus height",
    59  		},
    60  		[]string{},
    61  	)
    62  )
    63  
    64  func init() {
    65  	prometheus.MustRegister(_timeSlotMtc)
    66  	prometheus.MustRegister(_blockIntervalMtc)
    67  	prometheus.MustRegister(_consensusDurationMtc)
    68  	prometheus.MustRegister(_consensusHeightMtc)
    69  }
    70  
    71  type (
    72  	// NodesSelectionByEpochFunc defines a function to select nodes
    73  	NodesSelectionByEpochFunc func(uint64) ([]string, error)
    74  
    75  	// RDPoSCtx is the context of RollDPoS
    76  	RDPoSCtx interface {
    77  		consensusfsm.Context
    78  		Chain() ChainManager
    79  		BlockDeserializer() *block.Deserializer
    80  		RoundCalculator() *roundCalculator
    81  		Clock() clock.Clock
    82  		CheckBlockProposer(uint64, *blockProposal, *endorsement.Endorsement) error
    83  		CheckVoteEndorser(uint64, *ConsensusVote, *endorsement.Endorsement) error
    84  	}
    85  
    86  	rollDPoSCtx struct {
    87  		consensusfsm.ConsensusConfig
    88  
    89  		// TODO: explorer dependency deleted at #1085, need to add api params here
    90  		chain             ChainManager
    91  		blockDeserializer *block.Deserializer
    92  		broadcastHandler  scheme.Broadcast
    93  		roundCalc         *roundCalculator
    94  		eManagerDB        db.KVStore
    95  		toleratedOvertime time.Duration
    96  
    97  		encodedAddr string
    98  		priKey      crypto.PrivateKey
    99  		round       *roundCtx
   100  		clock       clock.Clock
   101  		active      bool
   102  		mutex       sync.RWMutex
   103  	}
   104  )
   105  
   106  // NewRollDPoSCtx returns a context of RollDPoSCtx
   107  func NewRollDPoSCtx(
   108  	cfg consensusfsm.ConsensusConfig,
   109  	consensusDBConfig db.Config,
   110  	active bool,
   111  	toleratedOvertime time.Duration,
   112  	timeBasedRotation bool,
   113  	chain ChainManager,
   114  	blockDeserializer *block.Deserializer,
   115  	rp *rolldpos.Protocol,
   116  	broadcastHandler scheme.Broadcast,
   117  	delegatesByEpochFunc NodesSelectionByEpochFunc,
   118  	proposersByEpochFunc NodesSelectionByEpochFunc,
   119  	encodedAddr string,
   120  	priKey crypto.PrivateKey,
   121  	clock clock.Clock,
   122  	beringHeight uint64,
   123  ) (RDPoSCtx, error) {
   124  	if chain == nil {
   125  		return nil, errors.New("chain cannot be nil")
   126  	}
   127  	if rp == nil {
   128  		return nil, errors.New("roll dpos protocol cannot be nil")
   129  	}
   130  	if clock == nil {
   131  		return nil, errors.New("clock cannot be nil")
   132  	}
   133  	if delegatesByEpochFunc == nil {
   134  		return nil, errors.New("delegates by epoch function cannot be nil")
   135  	}
   136  	if proposersByEpochFunc == nil {
   137  		return nil, errors.New("proposers by epoch function cannot be nil")
   138  	}
   139  	if cfg.AcceptBlockTTL(0)+cfg.AcceptProposalEndorsementTTL(0)+cfg.AcceptLockEndorsementTTL(0)+cfg.CommitTTL(0) > cfg.BlockInterval(0) {
   140  		return nil, errors.Errorf(
   141  			"invalid ttl config, the sum of ttls should be equal to block interval. acceptBlockTTL %d, acceptProposalEndorsementTTL %d, acceptLockEndorsementTTL %d, commitTTL %d, blockInterval %d",
   142  			cfg.AcceptBlockTTL(0),
   143  			cfg.AcceptProposalEndorsementTTL(0),
   144  			cfg.AcceptLockEndorsementTTL(0),
   145  			cfg.CommitTTL(0),
   146  			cfg.BlockInterval(0),
   147  		)
   148  	}
   149  	var eManagerDB db.KVStore
   150  	if len(consensusDBConfig.DbPath) > 0 {
   151  		eManagerDB = db.NewBoltDB(consensusDBConfig)
   152  	}
   153  	roundCalc := &roundCalculator{
   154  		delegatesByEpochFunc: delegatesByEpochFunc,
   155  		proposersByEpochFunc: proposersByEpochFunc,
   156  		chain:                chain,
   157  		rp:                   rp,
   158  		timeBasedRotation:    timeBasedRotation,
   159  		beringHeight:         beringHeight,
   160  	}
   161  	return &rollDPoSCtx{
   162  		ConsensusConfig:   cfg,
   163  		active:            active,
   164  		encodedAddr:       encodedAddr,
   165  		priKey:            priKey,
   166  		chain:             chain,
   167  		blockDeserializer: blockDeserializer,
   168  		broadcastHandler:  broadcastHandler,
   169  		clock:             clock,
   170  		roundCalc:         roundCalc,
   171  		eManagerDB:        eManagerDB,
   172  		toleratedOvertime: toleratedOvertime,
   173  	}, nil
   174  }
   175  
   176  func (ctx *rollDPoSCtx) Start(c context.Context) (err error) {
   177  	var eManager *endorsementManager
   178  	if ctx.eManagerDB != nil {
   179  		if err := ctx.eManagerDB.Start(c); err != nil {
   180  			return errors.Wrap(err, "Error when starting the collectionDB")
   181  		}
   182  		eManager, err = newEndorsementManager(ctx.eManagerDB, ctx.blockDeserializer)
   183  	}
   184  	ctx.round, err = ctx.roundCalc.NewRoundWithToleration(0, ctx.BlockInterval(0), ctx.clock.Now(), eManager, ctx.toleratedOvertime)
   185  
   186  	return err
   187  }
   188  
   189  func (ctx *rollDPoSCtx) Stop(c context.Context) error {
   190  	if ctx.eManagerDB != nil {
   191  		return ctx.eManagerDB.Stop(c)
   192  	}
   193  	return nil
   194  }
   195  
   196  func (ctx *rollDPoSCtx) Chain() ChainManager {
   197  	return ctx.chain
   198  }
   199  
   200  func (ctx *rollDPoSCtx) BlockDeserializer() *block.Deserializer {
   201  	return ctx.blockDeserializer
   202  }
   203  
   204  func (ctx *rollDPoSCtx) RoundCalculator() *roundCalculator {
   205  	return ctx.roundCalc
   206  }
   207  
   208  func (ctx *rollDPoSCtx) Clock() clock.Clock {
   209  	return ctx.clock
   210  }
   211  
   212  // CheckVoteEndorser checks if the endorsement's endorser is a valid delegate at the given height
   213  func (ctx *rollDPoSCtx) CheckVoteEndorser(
   214  	height uint64,
   215  	vote *ConsensusVote,
   216  	en *endorsement.Endorsement,
   217  ) error {
   218  	ctx.mutex.RLock()
   219  	defer ctx.mutex.RUnlock()
   220  	endorserAddr := en.Endorser().Address()
   221  	if endorserAddr == nil {
   222  		return errors.New("failed to get address")
   223  	}
   224  	if !ctx.roundCalc.IsDelegate(endorserAddr.String(), height) {
   225  		return errors.Errorf("%s is not delegate of the corresponding round", endorserAddr)
   226  	}
   227  
   228  	return nil
   229  }
   230  
   231  // CheckBlockProposer checks the block proposal
   232  func (ctx *rollDPoSCtx) CheckBlockProposer(
   233  	height uint64,
   234  	proposal *blockProposal,
   235  	en *endorsement.Endorsement,
   236  ) error {
   237  	ctx.mutex.RLock()
   238  	defer ctx.mutex.RUnlock()
   239  	if height != proposal.block.Height() {
   240  		return errors.Errorf(
   241  			"block height %d different from expected %d",
   242  			proposal.block.Height(),
   243  			height,
   244  		)
   245  	}
   246  	endorserAddr := en.Endorser().Address()
   247  	if endorserAddr == nil {
   248  		return errors.New("failed to get address")
   249  	}
   250  	if proposer := ctx.roundCalc.Proposer(height, ctx.BlockInterval(height), en.Timestamp()); proposer != endorserAddr.String() {
   251  		return errors.Errorf(
   252  			"%s is not proposer of the corresponding round, %s expected",
   253  			endorserAddr.String(),
   254  			proposer,
   255  		)
   256  	}
   257  	proposerAddr := proposal.ProposerAddress()
   258  	if ctx.roundCalc.Proposer(height, ctx.BlockInterval(height), proposal.block.Timestamp()) != proposerAddr {
   259  		return errors.Errorf("%s is not proposer of the corresponding round", proposerAddr)
   260  	}
   261  	if !proposal.block.VerifySignature() {
   262  		return errors.Errorf("invalid block signature")
   263  	}
   264  	if proposerAddr != endorserAddr.String() {
   265  		round, err := ctx.roundCalc.NewRound(height, ctx.BlockInterval(height), en.Timestamp(), nil)
   266  		if err != nil {
   267  			return err
   268  		}
   269  		if err := round.AddBlock(proposal.block); err != nil {
   270  			return err
   271  		}
   272  		blkHash := proposal.block.HashBlock()
   273  		for _, e := range proposal.proofOfLock {
   274  			if err := round.AddVoteEndorsement(
   275  				NewConsensusVote(blkHash[:], PROPOSAL),
   276  				e,
   277  			); err == nil {
   278  				continue
   279  			}
   280  			if err := round.AddVoteEndorsement(
   281  				NewConsensusVote(blkHash[:], COMMIT),
   282  				e,
   283  			); err != nil {
   284  				return err
   285  			}
   286  		}
   287  		if !round.EndorsedByMajority(blkHash[:], []ConsensusVoteTopic{PROPOSAL, COMMIT}) {
   288  			return errors.Wrap(ErrInsufficientEndorsements, "failed to verify proof of lock")
   289  		}
   290  	}
   291  	return nil
   292  }
   293  
   294  func (ctx *rollDPoSCtx) RoundCalc() *roundCalculator {
   295  	return ctx.roundCalc
   296  }
   297  
   298  /////////////////////////////////////
   299  // Context of consensusFSM interfaces
   300  /////////////////////////////////////
   301  
   302  func (ctx *rollDPoSCtx) NewConsensusEvent(
   303  	eventType fsm.EventType,
   304  	data interface{},
   305  ) *consensusfsm.ConsensusEvent {
   306  	ctx.mutex.RLock()
   307  	defer ctx.mutex.RUnlock()
   308  
   309  	return ctx.newConsensusEvent(eventType, data)
   310  }
   311  
   312  func (ctx *rollDPoSCtx) NewBackdoorEvt(
   313  	dst fsm.State,
   314  ) *consensusfsm.ConsensusEvent {
   315  	ctx.mutex.RLock()
   316  	defer ctx.mutex.RUnlock()
   317  
   318  	return ctx.newConsensusEvent(consensusfsm.BackdoorEvent, dst)
   319  }
   320  
   321  func (ctx *rollDPoSCtx) Logger() *zap.Logger {
   322  	ctx.mutex.RLock()
   323  	defer ctx.mutex.RUnlock()
   324  
   325  	return ctx.logger()
   326  }
   327  
   328  func (ctx *rollDPoSCtx) Prepare() error {
   329  	ctx.mutex.Lock()
   330  	defer ctx.mutex.Unlock()
   331  	height := ctx.chain.TipHeight() + 1
   332  	newRound, err := ctx.roundCalc.UpdateRound(ctx.round, height, ctx.BlockInterval(height), ctx.clock.Now(), ctx.toleratedOvertime)
   333  	if err != nil {
   334  		return err
   335  	}
   336  	ctx.logger().Debug(
   337  		"new round",
   338  		zap.Uint64("height", newRound.height),
   339  		zap.String("ts", ctx.clock.Now().String()),
   340  		zap.Uint64("epoch", newRound.epochNum),
   341  		zap.Uint64("epochStartHeight", newRound.epochStartHeight),
   342  		zap.Uint32("round", newRound.roundNum),
   343  		zap.String("roundStartTime", newRound.roundStartTime.String()),
   344  	)
   345  	ctx.round = newRound
   346  	_consensusHeightMtc.WithLabelValues().Set(float64(ctx.round.height))
   347  	_timeSlotMtc.WithLabelValues().Set(float64(ctx.round.roundNum))
   348  	return nil
   349  }
   350  
   351  func (ctx *rollDPoSCtx) IsDelegate() bool {
   352  	ctx.mutex.RLock()
   353  	defer ctx.mutex.RUnlock()
   354  
   355  	return ctx.isDelegate()
   356  }
   357  
   358  func (ctx *rollDPoSCtx) Proposal() (interface{}, error) {
   359  	ctx.mutex.RLock()
   360  	defer ctx.mutex.RUnlock()
   361  	if ctx.round.Proposer() != ctx.encodedAddr {
   362  		return nil, nil
   363  	}
   364  	if ctx.round.IsLocked() {
   365  		return ctx.endorseBlockProposal(newBlockProposal(
   366  			ctx.round.Block(ctx.round.HashOfBlockInLock()),
   367  			ctx.round.ProofOfLock(),
   368  		))
   369  	}
   370  	return ctx.mintNewBlock()
   371  }
   372  
   373  func (ctx *rollDPoSCtx) WaitUntilRoundStart() time.Duration {
   374  	ctx.mutex.RLock()
   375  	defer ctx.mutex.RUnlock()
   376  	now := ctx.clock.Now()
   377  	startTime := ctx.round.StartTime()
   378  	if now.Before(startTime) {
   379  		time.Sleep(startTime.Sub(now))
   380  		return 0
   381  	}
   382  	overTime := now.Sub(startTime)
   383  	if !ctx.isDelegate() && ctx.toleratedOvertime > overTime {
   384  		time.Sleep(ctx.toleratedOvertime - overTime)
   385  		return 0
   386  	}
   387  	return overTime
   388  }
   389  
   390  func (ctx *rollDPoSCtx) PreCommitEndorsement() interface{} {
   391  	ctx.mutex.RLock()
   392  	defer ctx.mutex.RUnlock()
   393  	endorsement := ctx.round.ReadyToCommit(ctx.encodedAddr)
   394  	if endorsement == nil {
   395  		// DON'T CHANGE, this is on purpose, because endorsement as nil won't result in a nil "interface {}"
   396  		return nil
   397  	}
   398  	return endorsement
   399  }
   400  
   401  func (ctx *rollDPoSCtx) NewProposalEndorsement(msg interface{}) (interface{}, error) {
   402  	ctx.mutex.RLock()
   403  	defer ctx.mutex.RUnlock()
   404  	var blockHash []byte
   405  	if msg != nil {
   406  		ecm, ok := msg.(*EndorsedConsensusMessage)
   407  		if !ok {
   408  			return nil, errors.New("invalid endorsed block")
   409  		}
   410  		proposal, ok := ecm.Document().(*blockProposal)
   411  		if !ok {
   412  			return nil, errors.New("invalid endorsed block")
   413  		}
   414  		blkHash := proposal.block.HashBlock()
   415  		blockHash = blkHash[:]
   416  		if err := ctx.chain.ValidateBlock(proposal.block); err != nil {
   417  			return nil, errors.Wrapf(err, "error when validating the proposed block")
   418  		}
   419  		if err := ctx.round.AddBlock(proposal.block); err != nil {
   420  			return nil, err
   421  		}
   422  		ctx.loggerWithStats().Debug("accept block proposal", log.Hex("block", blockHash))
   423  	} else if ctx.round.IsLocked() {
   424  		blockHash = ctx.round.HashOfBlockInLock()
   425  	}
   426  
   427  	return ctx.newEndorsement(
   428  		blockHash,
   429  		PROPOSAL,
   430  		ctx.round.StartTime().Add(ctx.AcceptBlockTTL(ctx.round.height)),
   431  	)
   432  }
   433  
   434  func (ctx *rollDPoSCtx) NewLockEndorsement(
   435  	msg interface{},
   436  ) (interface{}, error) {
   437  	ctx.mutex.RLock()
   438  	defer ctx.mutex.RUnlock()
   439  	blkHash, err := ctx.verifyVote(
   440  		msg,
   441  		[]ConsensusVoteTopic{PROPOSAL, COMMIT}, // commit is counted as one proposal
   442  	)
   443  	switch errors.Cause(err) {
   444  	case ErrInsufficientEndorsements:
   445  		return nil, nil
   446  	case nil:
   447  		if len(blkHash) != 0 {
   448  			ctx.loggerWithStats().Debug("Locked", log.Hex("block", blkHash))
   449  			return ctx.newEndorsement(
   450  				blkHash,
   451  				LOCK,
   452  				ctx.round.StartTime().Add(
   453  					ctx.AcceptBlockTTL(ctx.round.height)+ctx.AcceptProposalEndorsementTTL(ctx.round.height),
   454  				),
   455  			)
   456  		}
   457  		ctx.loggerWithStats().Debug("Unlocked")
   458  	}
   459  	return nil, err
   460  }
   461  
   462  func (ctx *rollDPoSCtx) NewPreCommitEndorsement(
   463  	msg interface{},
   464  ) (interface{}, error) {
   465  	ctx.mutex.RLock()
   466  	defer ctx.mutex.RUnlock()
   467  	blkHash, err := ctx.verifyVote(
   468  		msg,
   469  		[]ConsensusVoteTopic{LOCK, COMMIT}, // commit endorse is counted as one lock endorse
   470  	)
   471  	switch errors.Cause(err) {
   472  	case ErrInsufficientEndorsements:
   473  		return nil, nil
   474  	case nil:
   475  		ctx.loggerWithStats().Debug("Ready to pre-commit")
   476  		return ctx.newEndorsement(
   477  			blkHash,
   478  			COMMIT,
   479  			ctx.round.StartTime().Add(
   480  				ctx.AcceptBlockTTL(ctx.round.height)+ctx.AcceptProposalEndorsementTTL(ctx.round.height)+ctx.AcceptLockEndorsementTTL(ctx.round.height),
   481  			),
   482  		)
   483  	default:
   484  		return nil, err
   485  	}
   486  }
   487  
   488  func (ctx *rollDPoSCtx) Commit(msg interface{}) (bool, error) {
   489  	ctx.mutex.Lock()
   490  	defer ctx.mutex.Unlock()
   491  	blkHash, err := ctx.verifyVote(msg, []ConsensusVoteTopic{COMMIT})
   492  	switch errors.Cause(err) {
   493  	case ErrInsufficientEndorsements:
   494  		return false, nil
   495  	case nil:
   496  		ctx.loggerWithStats().Debug("Ready to commit")
   497  	default:
   498  		return false, err
   499  	}
   500  	// this is redudant check for now, as we only accept endorsements of the received blocks
   501  	pendingBlock := ctx.round.Block(blkHash)
   502  	if pendingBlock == nil {
   503  		return false, nil
   504  	}
   505  	if ctx.round.Height()%100 == 0 {
   506  		ctx.logger().Info("consensus reached", zap.Uint64("blockHeight", ctx.round.Height()))
   507  	}
   508  	if err := pendingBlock.Finalize(
   509  		ctx.round.Endorsements(blkHash, []ConsensusVoteTopic{COMMIT}),
   510  		ctx.round.StartTime().Add(
   511  			ctx.AcceptBlockTTL(ctx.round.height)+ctx.AcceptProposalEndorsementTTL(ctx.round.height)+ctx.AcceptLockEndorsementTTL(ctx.round.height),
   512  		),
   513  	); err != nil {
   514  		return false, errors.Wrap(err, "failed to add endorsements to block")
   515  	}
   516  
   517  	// Commit and broadcast the pending block
   518  	switch err := ctx.chain.CommitBlock(pendingBlock); errors.Cause(err) {
   519  	case blockchain.ErrInvalidTipHeight:
   520  		return true, nil
   521  	case nil:
   522  		break
   523  	default:
   524  		log.L().Error("error when committing the block", zap.Error(err))
   525  		return false, errors.Wrap(err, "error when committing a block")
   526  	}
   527  	// Broadcast the committed block to the network
   528  	if blkProto := pendingBlock.ConvertToBlockPb(); blkProto != nil {
   529  		if err := ctx.broadcastHandler(blkProto); err != nil {
   530  			ctx.logger().Error(
   531  				"error when broadcasting blkProto",
   532  				zap.Error(err),
   533  				zap.Uint64("block", pendingBlock.Height()),
   534  			)
   535  		}
   536  		// putblock to parent chain if the current node is proposer and current chain is a sub chain
   537  		if ctx.round.Proposer() == ctx.encodedAddr && ctx.chain.ChainAddress() != "" {
   538  			// TODO: explorer dependency deleted at #1085, need to call putblock related method
   539  		}
   540  	} else {
   541  		ctx.logger().Panic(
   542  			"error when converting a block into a proto msg",
   543  			zap.Uint64("block", pendingBlock.Height()),
   544  		)
   545  	}
   546  
   547  	_consensusDurationMtc.WithLabelValues().Set(float64(time.Since(ctx.round.roundStartTime)))
   548  	if pendingBlock.Height() > 1 {
   549  		prevBlkProposeTime, err := ctx.chain.BlockProposeTime(pendingBlock.Height() - 1)
   550  		if err != nil {
   551  			ctx.logger().Error("Error when getting the previous block header.",
   552  				zap.Error(err),
   553  				zap.Uint64("height", pendingBlock.Height()-1),
   554  			)
   555  		}
   556  		_blockIntervalMtc.WithLabelValues().Set(float64(pendingBlock.Timestamp().Sub(prevBlkProposeTime)))
   557  	}
   558  	return true, nil
   559  }
   560  
   561  func (ctx *rollDPoSCtx) Broadcast(endorsedMsg interface{}) {
   562  	ctx.mutex.RLock()
   563  	defer ctx.mutex.RUnlock()
   564  	ecm, ok := endorsedMsg.(*EndorsedConsensusMessage)
   565  	if !ok {
   566  		ctx.loggerWithStats().Error("invalid message type", zap.Any("message", ecm))
   567  		return
   568  	}
   569  	msg, err := ecm.Proto()
   570  	if err != nil {
   571  		ctx.loggerWithStats().Error("failed to generate protobuf message", zap.Error(err))
   572  		return
   573  	}
   574  	if err := ctx.broadcastHandler(msg); err != nil {
   575  		ctx.loggerWithStats().Error("fail to broadcast", zap.Error(err))
   576  	}
   577  }
   578  
   579  func (ctx *rollDPoSCtx) IsStaleEvent(evt *consensusfsm.ConsensusEvent) bool {
   580  	ctx.mutex.RLock()
   581  	defer ctx.mutex.RUnlock()
   582  
   583  	return ctx.round.IsStale(evt.Height(), evt.Round(), evt.Data())
   584  }
   585  
   586  func (ctx *rollDPoSCtx) IsFutureEvent(evt *consensusfsm.ConsensusEvent) bool {
   587  	ctx.mutex.RLock()
   588  	defer ctx.mutex.RUnlock()
   589  
   590  	return ctx.round.IsFuture(evt.Height(), evt.Round())
   591  }
   592  
   593  func (ctx *rollDPoSCtx) IsStaleUnmatchedEvent(evt *consensusfsm.ConsensusEvent) bool {
   594  	ctx.mutex.RLock()
   595  	defer ctx.mutex.RUnlock()
   596  
   597  	return ctx.clock.Now().Sub(evt.Timestamp()) > ctx.UnmatchedEventTTL(ctx.round.height)
   598  }
   599  
   600  func (ctx *rollDPoSCtx) Height() uint64 {
   601  	ctx.mutex.RLock()
   602  	defer ctx.mutex.RUnlock()
   603  
   604  	return ctx.round.Height()
   605  }
   606  
   607  func (ctx *rollDPoSCtx) Activate(active bool) {
   608  	ctx.mutex.Lock()
   609  	defer ctx.mutex.Unlock()
   610  
   611  	ctx.active = active
   612  }
   613  
   614  func (ctx *rollDPoSCtx) Active() bool {
   615  	ctx.mutex.RLock()
   616  	defer ctx.mutex.RUnlock()
   617  
   618  	return ctx.active
   619  }
   620  
   621  ///////////////////////////////////////////
   622  // private functions
   623  ///////////////////////////////////////////
   624  
   625  func (ctx *rollDPoSCtx) mintNewBlock() (*EndorsedConsensusMessage, error) {
   626  	var err error
   627  	blk := ctx.round.CachedMintedBlock()
   628  	if blk == nil {
   629  		// in case that there is no cached block in eManagerDB, it mints a new block.
   630  		blk, err = ctx.chain.MintNewBlock(ctx.round.StartTime())
   631  		if err != nil {
   632  			return nil, err
   633  		}
   634  		if err = ctx.round.SetMintedBlock(blk); err != nil {
   635  			return nil, err
   636  		}
   637  	}
   638  
   639  	var proofOfUnlock []*endorsement.Endorsement
   640  	if ctx.round.IsUnlocked() {
   641  		proofOfUnlock = ctx.round.ProofOfLock()
   642  	}
   643  	return ctx.endorseBlockProposal(newBlockProposal(blk, proofOfUnlock))
   644  }
   645  
   646  func (ctx *rollDPoSCtx) isDelegate() bool {
   647  	if active := ctx.active; !active {
   648  		ctx.logger().Info("current node is in standby mode")
   649  		return false
   650  	}
   651  	return ctx.round.IsDelegate(ctx.encodedAddr)
   652  }
   653  
   654  func (ctx *rollDPoSCtx) endorseBlockProposal(proposal *blockProposal) (*EndorsedConsensusMessage, error) {
   655  	en, err := endorsement.Endorse(ctx.priKey, proposal, ctx.round.StartTime())
   656  	if err != nil {
   657  		return nil, err
   658  	}
   659  	return NewEndorsedConsensusMessage(proposal.block.Height(), proposal, en), nil
   660  }
   661  
   662  func (ctx *rollDPoSCtx) logger() *zap.Logger {
   663  	return ctx.round.Log(log.Logger("consensus"))
   664  }
   665  
   666  func (ctx *rollDPoSCtx) newConsensusEvent(
   667  	eventType fsm.EventType,
   668  	data interface{},
   669  ) *consensusfsm.ConsensusEvent {
   670  	switch ed := data.(type) {
   671  	case *EndorsedConsensusMessage:
   672  		height := ed.Height()
   673  		roundNum, _, err := ctx.roundCalc.RoundInfo(height, ctx.BlockInterval(height), ed.Endorsement().Timestamp())
   674  		if err != nil {
   675  			ctx.logger().Error(
   676  				"failed to calculate round for generating consensus event",
   677  				zap.String("eventType", string(eventType)),
   678  				zap.Uint64("height", ed.Height()),
   679  				zap.String("timestamp", ed.Endorsement().Timestamp().String()),
   680  				zap.Any("data", data),
   681  				zap.Error(err),
   682  			)
   683  			return nil
   684  		}
   685  		return consensusfsm.NewConsensusEvent(
   686  			eventType,
   687  			data,
   688  			ed.Height(),
   689  			roundNum,
   690  			ctx.clock.Now(),
   691  		)
   692  	default:
   693  		return consensusfsm.NewConsensusEvent(
   694  			eventType,
   695  			data,
   696  			ctx.round.Height(),
   697  			ctx.round.Number(),
   698  			ctx.clock.Now(),
   699  		)
   700  	}
   701  }
   702  
   703  func (ctx *rollDPoSCtx) loggerWithStats() *zap.Logger {
   704  	return ctx.round.LogWithStats(log.Logger("consensus"))
   705  }
   706  
   707  func (ctx *rollDPoSCtx) verifyVote(
   708  	msg interface{},
   709  	topics []ConsensusVoteTopic,
   710  ) ([]byte, error) {
   711  	consensusMsg, ok := msg.(*EndorsedConsensusMessage)
   712  	if !ok {
   713  		return nil, errors.New("invalid msg")
   714  	}
   715  	vote, ok := consensusMsg.Document().(*ConsensusVote)
   716  	if !ok {
   717  		return nil, errors.New("invalid msg")
   718  	}
   719  	blkHash := vote.BlockHash()
   720  	endorsement := consensusMsg.Endorsement()
   721  	if err := ctx.round.AddVoteEndorsement(vote, endorsement); err != nil {
   722  		return blkHash, err
   723  	}
   724  	ctx.loggerWithStats().Debug(
   725  		"verified consensus vote",
   726  		log.Hex("block", blkHash),
   727  		zap.Uint8("topic", uint8(vote.Topic())),
   728  		zap.String("endorser", endorsement.Endorser().HexString()),
   729  	)
   730  	if !ctx.round.EndorsedByMajority(blkHash, topics) {
   731  		return blkHash, ErrInsufficientEndorsements
   732  	}
   733  	return blkHash, nil
   734  }
   735  
   736  func (ctx *rollDPoSCtx) newEndorsement(
   737  	blkHash []byte,
   738  	topic ConsensusVoteTopic,
   739  	timestamp time.Time,
   740  ) (*EndorsedConsensusMessage, error) {
   741  	vote := NewConsensusVote(
   742  		blkHash,
   743  		topic,
   744  	)
   745  	en, err := endorsement.Endorse(ctx.priKey, vote, timestamp)
   746  	if err != nil {
   747  		return nil, err
   748  	}
   749  
   750  	return NewEndorsedConsensusMessage(ctx.round.Height(), vote, en), nil
   751  }