github.com/iotexproject/iotex-core@v1.14.1-rc1/consensus/scheme/rolldpos/roundcalculator.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 13 "github.com/iotexproject/iotex-core/action/protocol/rolldpos" 14 "github.com/iotexproject/iotex-core/endorsement" 15 ) 16 17 var errInvalidCurrentTime = errors.New("invalid current time") 18 19 type roundCalculator struct { 20 chain ChainManager 21 timeBasedRotation bool 22 rp *rolldpos.Protocol 23 delegatesByEpochFunc NodesSelectionByEpochFunc 24 proposersByEpochFunc NodesSelectionByEpochFunc 25 beringHeight uint64 26 } 27 28 // UpdateRound updates previous roundCtx 29 func (c *roundCalculator) UpdateRound(round *roundCtx, height uint64, blockInterval time.Duration, now time.Time, toleratedOvertime time.Duration) (*roundCtx, error) { 30 epochNum := round.EpochNum() 31 epochStartHeight := round.EpochStartHeight() 32 delegates := round.Delegates() 33 proposers := round.Proposers() 34 switch { 35 case height < round.Height(): 36 return nil, errors.New("cannot update to a lower height") 37 case height == round.Height(): 38 if now.Before(round.StartTime()) { 39 return round, nil 40 } 41 default: 42 if height >= round.NextEpochStartHeight() { 43 // update the epoch 44 epochNum = c.rp.GetEpochNum(height) 45 epochStartHeight = c.rp.GetEpochHeight(epochNum) 46 var err error 47 if delegates, err = c.Delegates(height); err != nil { 48 return nil, err 49 } 50 if proposers, err = c.Proposers(height); err != nil { 51 return nil, err 52 } 53 } 54 } 55 roundNum, roundStartTime, err := c.roundInfo(height, blockInterval, now, toleratedOvertime) 56 if err != nil { 57 return nil, err 58 } 59 proposer, err := c.calculateProposer(height, roundNum, proposers) 60 if err != nil { 61 return nil, err 62 } 63 var status status 64 var blockInLock []byte 65 var proofOfLock []*endorsement.Endorsement 66 if height == round.Height() { 67 err = round.eManager.Cleanup(roundStartTime) 68 if err != nil { 69 return nil, err 70 } 71 status = round.status 72 blockInLock = round.blockInLock 73 proofOfLock = round.proofOfLock 74 } else { 75 err = round.eManager.Cleanup(time.Time{}) 76 if err != nil { 77 return nil, err 78 } 79 } 80 return &roundCtx{ 81 epochNum: epochNum, 82 epochStartHeight: epochStartHeight, 83 nextEpochStartHeight: c.rp.GetEpochHeight(epochNum + 1), 84 delegates: delegates, 85 proposers: proposers, 86 87 height: height, 88 roundNum: roundNum, 89 proposer: proposer, 90 roundStartTime: roundStartTime, 91 nextRoundStartTime: roundStartTime.Add(blockInterval), 92 eManager: round.eManager, 93 status: status, 94 blockInLock: blockInLock, 95 proofOfLock: proofOfLock, 96 }, nil 97 } 98 99 // Proposer returns the block producer of the round 100 func (c *roundCalculator) Proposer(height uint64, blockInterval time.Duration, roundStartTime time.Time) string { 101 round, err := c.newRound(height, blockInterval, roundStartTime, nil, 0) 102 if err != nil { 103 return "" 104 } 105 106 return round.Proposer() 107 } 108 109 func (c *roundCalculator) IsDelegate(addr string, height uint64) bool { 110 delegates, err := c.Delegates(height) 111 if err != nil { 112 return false 113 } 114 for _, d := range delegates { 115 if addr == d { 116 return true 117 } 118 } 119 120 return false 121 } 122 123 // RoundInfo returns information of round by the given height and current time 124 func (c *roundCalculator) RoundInfo( 125 height uint64, 126 blockInterval time.Duration, 127 now time.Time, 128 ) (roundNum uint32, roundStartTime time.Time, err error) { 129 return c.roundInfo(height, blockInterval, now, 0) 130 } 131 132 func (c *roundCalculator) roundInfo( 133 height uint64, 134 blockInterval time.Duration, 135 now time.Time, 136 toleratedOvertime time.Duration, 137 ) (roundNum uint32, roundStartTime time.Time, err error) { 138 var lastBlockTime time.Time 139 if lastBlockTime, err = c.chain.BlockProposeTime(0); err != nil { 140 return 141 } 142 if height > 1 { 143 if height >= c.beringHeight { 144 var lastBlkProposeTime time.Time 145 if lastBlkProposeTime, err = c.chain.BlockProposeTime(height - 1); err != nil { 146 return 147 } 148 lastBlockTime = lastBlockTime.Add(lastBlkProposeTime.Sub(lastBlockTime) / blockInterval * blockInterval) 149 } else { 150 var lastBlkCommitTime time.Time 151 if lastBlkCommitTime, err = c.chain.BlockCommitTime(height - 1); err != nil { 152 return 153 } 154 lastBlockTime = lastBlockTime.Add(lastBlkCommitTime.Sub(lastBlockTime) / blockInterval * blockInterval) 155 } 156 } 157 if !lastBlockTime.Before(now) { 158 // TODO: if this is the case, it is possible that the system time is far behind the time of other nodes. 159 // better error handling may be needed on the caller side 160 err = errors.Wrapf( 161 errInvalidCurrentTime, 162 "last block time %s is after than current time %s", 163 lastBlockTime, 164 now, 165 ) 166 return 167 } 168 duration := now.Sub(lastBlockTime) 169 if duration > blockInterval { 170 roundNum = uint32(duration / blockInterval) 171 if toleratedOvertime == 0 || duration%blockInterval < toleratedOvertime { 172 roundNum-- 173 } 174 } 175 roundStartTime = lastBlockTime.Add(time.Duration(roundNum+1) * blockInterval) 176 177 return roundNum, roundStartTime, nil 178 } 179 180 // Delegates returns list of delegates at given height 181 func (c *roundCalculator) Delegates(height uint64) ([]string, error) { 182 epochNum := c.rp.GetEpochNum(height) 183 return c.delegatesByEpochFunc(epochNum) 184 } 185 186 // Proposers returns list of candidate proposers at given height 187 func (c *roundCalculator) Proposers(height uint64) ([]string, error) { 188 epochNum := c.rp.GetEpochNum(height) 189 return c.proposersByEpochFunc(epochNum) 190 } 191 192 // NewRoundWithToleration starts new round with tolerated over time 193 func (c *roundCalculator) NewRoundWithToleration( 194 height uint64, 195 blockInterval time.Duration, 196 now time.Time, 197 eManager *endorsementManager, 198 toleratedOvertime time.Duration, 199 ) (round *roundCtx, err error) { 200 return c.newRound(height, blockInterval, now, eManager, toleratedOvertime) 201 } 202 203 // NewRound starts new round and returns roundCtx 204 func (c *roundCalculator) NewRound( 205 height uint64, 206 blockInterval time.Duration, 207 now time.Time, 208 eManager *endorsementManager, 209 ) (round *roundCtx, err error) { 210 return c.newRound(height, blockInterval, now, eManager, 0) 211 } 212 213 func (c *roundCalculator) newRound( 214 height uint64, 215 blockInterval time.Duration, 216 now time.Time, 217 eManager *endorsementManager, 218 toleratedOvertime time.Duration, 219 ) (round *roundCtx, err error) { 220 epochNum := uint64(0) 221 epochStartHeight := uint64(0) 222 var delegates, proposers []string 223 var roundNum uint32 224 var proposer string 225 var roundStartTime time.Time 226 if height != 0 { 227 epochNum = c.rp.GetEpochNum(height) 228 epochStartHeight = c.rp.GetEpochHeight(epochNum) 229 if delegates, err = c.Delegates(height); err != nil { 230 return 231 } 232 if proposers, err = c.Proposers(height); err != nil { 233 return 234 } 235 if roundNum, roundStartTime, err = c.roundInfo(height, blockInterval, now, toleratedOvertime); err != nil { 236 return 237 } 238 if proposer, err = c.calculateProposer(height, roundNum, proposers); err != nil { 239 return 240 } 241 } 242 if eManager == nil { 243 if eManager, err = newEndorsementManager(nil, nil); err != nil { 244 return nil, err 245 } 246 } 247 round = &roundCtx{ 248 epochNum: epochNum, 249 epochStartHeight: epochStartHeight, 250 nextEpochStartHeight: c.rp.GetEpochHeight(epochNum + 1), 251 delegates: delegates, 252 proposers: proposers, 253 254 height: height, 255 roundNum: roundNum, 256 proposer: proposer, 257 eManager: eManager, 258 roundStartTime: roundStartTime, 259 nextRoundStartTime: roundStartTime.Add(blockInterval), 260 status: _open, 261 } 262 eManager.SetIsMarjorityFunc(round.EndorsedByMajority) 263 264 return round, nil 265 } 266 267 // calculateProposer calulates proposer according to height and round number 268 func (c *roundCalculator) calculateProposer( 269 height uint64, 270 round uint32, 271 proposers []string, 272 ) (proposer string, err error) { 273 // TODO use number of proposers 274 numProposers := c.rp.NumDelegates() 275 if numProposers != uint64(len(proposers)) { 276 err = errors.New("invalid proposer list") 277 return 278 } 279 idx := height 280 if c.timeBasedRotation { 281 idx += uint64(round) 282 } 283 proposer = proposers[idx%numProposers] 284 return 285 }