github.com/iotexproject/iotex-core@v1.14.1-rc1/consensus/scheme/rolldpos/roundctx.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  	"time"
    10  
    11  	"github.com/pkg/errors"
    12  	"go.uber.org/zap"
    13  
    14  	"github.com/iotexproject/iotex-core/blockchain/block"
    15  	"github.com/iotexproject/iotex-core/endorsement"
    16  )
    17  
    18  // ErrInsufficientEndorsements represents the error that not enough endorsements
    19  var ErrInsufficientEndorsements = errors.New("Insufficient endorsements")
    20  
    21  type status int
    22  
    23  const (
    24  	_open status = iota
    25  	_locked
    26  	_unlocked
    27  )
    28  
    29  // roundCtx keeps the context data for the current round and block.
    30  type roundCtx struct {
    31  	epochNum             uint64
    32  	epochStartHeight     uint64
    33  	nextEpochStartHeight uint64
    34  	delegates            []string
    35  	proposers            []string
    36  
    37  	height             uint64
    38  	roundNum           uint32
    39  	proposer           string
    40  	roundStartTime     time.Time
    41  	nextRoundStartTime time.Time
    42  
    43  	blockInLock []byte
    44  	proofOfLock []*endorsement.Endorsement
    45  	status      status
    46  	eManager    *endorsementManager
    47  }
    48  
    49  func (ctx *roundCtx) Log(l *zap.Logger) *zap.Logger {
    50  	return l.With(
    51  		zap.Uint64("height", ctx.height),
    52  		zap.Uint64("epoch", ctx.epochNum),
    53  		zap.Uint32("round", ctx.roundNum),
    54  		zap.String("proposer", ctx.proposer),
    55  	)
    56  }
    57  
    58  func (ctx *roundCtx) LogWithStats(l *zap.Logger) *zap.Logger {
    59  	return ctx.eManager.Log(ctx.Log(l), ctx.delegates)
    60  }
    61  
    62  func (ctx *roundCtx) EpochNum() uint64 {
    63  	return ctx.epochNum
    64  }
    65  
    66  func (ctx *roundCtx) EpochStartHeight() uint64 {
    67  	return ctx.epochStartHeight
    68  }
    69  
    70  func (ctx *roundCtx) NextEpochStartHeight() uint64 {
    71  	return ctx.nextEpochStartHeight
    72  }
    73  
    74  func (ctx *roundCtx) StartTime() time.Time {
    75  	return ctx.roundStartTime
    76  }
    77  
    78  func (ctx *roundCtx) NextRoundStartTime() time.Time {
    79  	return ctx.nextRoundStartTime
    80  }
    81  
    82  func (ctx *roundCtx) Height() uint64 {
    83  	return ctx.height
    84  }
    85  
    86  func (ctx *roundCtx) Number() uint32 {
    87  	return ctx.roundNum
    88  }
    89  
    90  func (ctx *roundCtx) Proposer() string {
    91  	return ctx.proposer
    92  }
    93  
    94  func (ctx *roundCtx) Delegates() []string {
    95  	return ctx.delegates
    96  }
    97  
    98  func (ctx *roundCtx) Proposers() []string {
    99  	return ctx.proposers
   100  }
   101  
   102  func (ctx *roundCtx) IsDelegate(addr string) bool {
   103  	for _, d := range ctx.delegates {
   104  		if addr == d {
   105  			return true
   106  		}
   107  	}
   108  
   109  	return false
   110  }
   111  
   112  func (ctx *roundCtx) Block(blkHash []byte) *block.Block {
   113  	return ctx.block(blkHash)
   114  }
   115  
   116  func (ctx *roundCtx) Endorsements(blkHash []byte, topics []ConsensusVoteTopic) []*endorsement.Endorsement {
   117  	return ctx.endorsements(blkHash, topics)
   118  }
   119  
   120  func (ctx *roundCtx) IsLocked() bool {
   121  	return ctx.status == _locked
   122  }
   123  
   124  func (ctx *roundCtx) IsUnlocked() bool {
   125  	return ctx.status == _unlocked
   126  }
   127  
   128  func (ctx *roundCtx) ReadyToCommit(addr string) *EndorsedConsensusMessage {
   129  	c := ctx.eManager.CollectionByBlockHash(ctx.blockInLock)
   130  	if c == nil {
   131  		return nil
   132  	}
   133  	en := c.Endorsement(addr, COMMIT)
   134  	if en == nil {
   135  		return nil
   136  	}
   137  	blk := c.Block()
   138  	if blk == nil {
   139  		return nil
   140  	}
   141  	blkHash := blk.HashBlock()
   142  	return NewEndorsedConsensusMessage(
   143  		blk.Height(),
   144  		NewConsensusVote(blkHash[:], COMMIT),
   145  		en,
   146  	)
   147  }
   148  
   149  func (ctx *roundCtx) HashOfBlockInLock() []byte {
   150  	return ctx.blockInLock
   151  }
   152  
   153  func (ctx *roundCtx) ProofOfLock() []*endorsement.Endorsement {
   154  	return ctx.proofOfLock
   155  }
   156  
   157  func (ctx *roundCtx) IsStale(height uint64, num uint32, data interface{}) bool {
   158  	switch {
   159  	case height < ctx.height:
   160  		return true
   161  	case height > ctx.height:
   162  		return false
   163  	case num >= ctx.roundNum:
   164  		return false
   165  	default:
   166  		msg, ok := data.(*EndorsedConsensusMessage)
   167  		if !ok {
   168  			return true
   169  		}
   170  		vote, ok := msg.Document().(*ConsensusVote)
   171  		if !ok {
   172  			return true
   173  		}
   174  
   175  		return vote.Topic() != COMMIT
   176  	}
   177  }
   178  
   179  func (ctx *roundCtx) IsFuture(height uint64, num uint32) bool {
   180  	if height > ctx.height || height == ctx.height && num > ctx.roundNum {
   181  		return true
   182  	}
   183  	return false
   184  }
   185  
   186  func (ctx *roundCtx) EndorsedByMajority(
   187  	blockHash []byte,
   188  	topics []ConsensusVoteTopic,
   189  ) bool {
   190  	return ctx.endorsedByMajority(blockHash, topics)
   191  }
   192  
   193  func (ctx *roundCtx) AddBlock(blk *block.Block) error {
   194  	return ctx.eManager.RegisterBlock(blk)
   195  }
   196  
   197  func (ctx *roundCtx) AddVoteEndorsement(
   198  	vote *ConsensusVote,
   199  	en *endorsement.Endorsement,
   200  ) error {
   201  	if !endorsement.VerifyEndorsement(vote, en) {
   202  		return errors.New("invalid endorsement for the vote")
   203  	}
   204  	if addr := en.Endorser().Address(); addr == nil || !ctx.IsDelegate(addr.String()) {
   205  		return errors.New("invalid endorser")
   206  	}
   207  	blockHash := vote.BlockHash()
   208  	// TODO: (zhi) request for block
   209  	if len(blockHash) != 0 && ctx.block(blockHash) == nil {
   210  		return errors.New("the corresponding block not received")
   211  	}
   212  	if err := ctx.eManager.AddVoteEndorsement(vote, en); err != nil {
   213  		return err
   214  	}
   215  	if vote.Topic() == LOCK {
   216  		return nil
   217  	}
   218  	if len(blockHash) != 0 && ctx.status == _locked {
   219  		return nil
   220  	}
   221  	endorsements := ctx.endorsements(
   222  		blockHash,
   223  		[]ConsensusVoteTopic{PROPOSAL, COMMIT},
   224  	)
   225  	if !ctx.isMajority(endorsements) {
   226  		return nil
   227  	}
   228  	if len(blockHash) == 0 {
   229  		// TODO: (zhi) look into details of unlock
   230  		ctx.status = _unlocked
   231  	} else {
   232  		ctx.status = _locked
   233  	}
   234  	ctx.blockInLock = blockHash
   235  	ctx.proofOfLock = endorsements
   236  
   237  	return nil
   238  }
   239  
   240  func (ctx *roundCtx) SetMintedBlock(blk *block.Block) error {
   241  	return ctx.eManager.SetMintedBlock(blk)
   242  }
   243  
   244  func (ctx *roundCtx) CachedMintedBlock() *block.Block {
   245  	return ctx.eManager.CachedMintedBlock()
   246  }
   247  
   248  // private functions
   249  
   250  func (ctx *roundCtx) endorsements(blkHash []byte, topics []ConsensusVoteTopic) []*endorsement.Endorsement {
   251  	c := ctx.eManager.CollectionByBlockHash(blkHash)
   252  	if c == nil {
   253  		return []*endorsement.Endorsement{}
   254  	}
   255  	return c.Endorsements(topics)
   256  }
   257  
   258  func (ctx *roundCtx) endorsedByMajority(blockHash []byte, topics []ConsensusVoteTopic) bool {
   259  	return ctx.isMajority(ctx.endorsements(blockHash, topics))
   260  }
   261  
   262  func (ctx *roundCtx) isMajority(endorsements []*endorsement.Endorsement) bool {
   263  	return 3*len(endorsements) > 2*len(ctx.delegates)
   264  }
   265  
   266  func (ctx *roundCtx) block(blkHash []byte) *block.Block {
   267  	c := ctx.eManager.CollectionByBlockHash(blkHash)
   268  	if c == nil {
   269  		return nil
   270  	}
   271  	return c.Block()
   272  }