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 }