github.com/electroneum/electroneum-sc@v0.0.0-20230105223411-3bc1d078281e/consensus/istanbul/core/core.go (about) 1 // Copyright 2017 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package core 18 19 import ( 20 "math" 21 "math/big" 22 "sync" 23 "time" 24 25 "github.com/electroneum/electroneum-sc/common" 26 "github.com/electroneum/electroneum-sc/consensus/istanbul" 27 qbfttypes "github.com/electroneum/electroneum-sc/consensus/istanbul/types" 28 "github.com/electroneum/electroneum-sc/core/types" 29 "github.com/electroneum/electroneum-sc/event" 30 "github.com/electroneum/electroneum-sc/log" 31 metrics "github.com/electroneum/electroneum-sc/metrics" 32 "gopkg.in/karalabe/cookiejar.v2/collections/prque" 33 ) 34 35 var ( 36 roundMeter = metrics.NewRegisteredMeter("consensus/istanbul/qbft/core/round", nil) 37 sequenceMeter = metrics.NewRegisteredMeter("consensus/istanbul/qbft/core/sequence", nil) 38 consensusTimer = metrics.NewRegisteredTimer("consensus/istanbul/qbft/core/consensus", nil) 39 ) 40 41 // New creates an Istanbul consensus core 42 func New(backend istanbul.Backend, config *istanbul.Config) istanbul.Core { 43 c := &core{ 44 config: config, 45 address: backend.Address(), 46 state: StateAcceptRequest, 47 handlerWg: new(sync.WaitGroup), 48 logger: log.New("address", backend.Address()), 49 backend: backend, 50 backlogs: make(map[common.Address]*prque.Prque), 51 backlogsMu: new(sync.Mutex), 52 pendingRequests: prque.New(), 53 pendingRequestsMu: new(sync.Mutex), 54 consensusTimestamp: time.Time{}, 55 } 56 57 c.validateFn = c.checkValidatorSignature 58 return c 59 } 60 61 // ---------------------------------------------------------------------------- 62 63 type core struct { 64 config *istanbul.Config 65 address common.Address 66 state State 67 logger log.Logger 68 69 backend istanbul.Backend 70 events *event.TypeMuxSubscription 71 finalCommittedSub *event.TypeMuxSubscription 72 timeoutSub *event.TypeMuxSubscription 73 futurePreprepareTimer *time.Timer 74 75 valSet istanbul.ValidatorSet 76 validateFn func([]byte, []byte) (common.Address, error) 77 78 backlogs map[common.Address]*prque.Prque 79 backlogsMu *sync.Mutex 80 81 current *roundState 82 handlerWg *sync.WaitGroup 83 84 roundChangeSet *roundChangeSet 85 roundChangeTimer *time.Timer 86 87 QBFTPreparedPrepares []*qbfttypes.Prepare 88 89 pendingRequests *prque.Prque 90 pendingRequestsMu *sync.Mutex 91 92 consensusTimestamp time.Time 93 } 94 95 func (c *core) currentView() *istanbul.View { 96 return &istanbul.View{ 97 Sequence: new(big.Int).Set(c.current.Sequence()), 98 Round: new(big.Int).Set(c.current.Round()), 99 } 100 } 101 102 func (c *core) IsProposer() bool { 103 v := c.valSet 104 if v == nil { 105 return false 106 } 107 return v.IsProposer(c.backend.Address()) 108 } 109 110 func (c *core) IsCurrentProposal(blockHash common.Hash) bool { 111 return c.current != nil && c.current.pendingRequest != nil && c.current.pendingRequest.Proposal.Hash() == blockHash 112 } 113 114 // startNewRound starts a new round. if round equals to 0, it means to starts a new sequence 115 func (c *core) startNewRound(round *big.Int) { 116 var logger log.Logger 117 if c.current == nil { 118 logger = c.logger.New("old.round", -1, "old.seq", 0) 119 } else { 120 logger = c.currentLogger(false, nil) 121 } 122 logger = logger.New("target.round", round) 123 124 roundChange := false 125 126 // Try to get last proposal 127 lastProposal, lastProposer := c.backend.LastProposal() 128 if lastProposal != nil { 129 logger = logger.New("lastProposal.number", lastProposal.Number().Uint64(), "lastProposal.hash", lastProposal.Hash()) 130 } 131 132 logger.Info("IBFT: initialize new round") 133 134 if c.current == nil { 135 logger.Debug("IBFT: start at the initial round") 136 } else if lastProposal.Number().Cmp(c.current.Sequence()) >= 0 { 137 diff := new(big.Int).Sub(lastProposal.Number(), c.current.Sequence()) 138 sequenceMeter.Mark(new(big.Int).Add(diff, common.Big1).Int64()) 139 140 if !c.consensusTimestamp.IsZero() { 141 consensusTimer.UpdateSince(c.consensusTimestamp) 142 c.consensusTimestamp = time.Time{} 143 } 144 logger.Debug("IBFT: catch up last block proposal") 145 } else if lastProposal.Number().Cmp(big.NewInt(c.current.Sequence().Int64()-1)) == 0 { 146 if round.Cmp(common.Big0) == 0 { 147 // same seq and round, don't need to start new round 148 logger.Debug("IBFT: same round, no need to start new round") 149 return 150 } else if round.Cmp(c.current.Round()) < 0 { 151 logger.Warn("IBFT: next round is inferior to current round") 152 return 153 } 154 roundChange = true 155 } else { 156 logger.Warn("IBFT: next sequence is before last block proposal") 157 return 158 } 159 160 var oldLogger log.Logger 161 if c.current == nil { 162 oldLogger = c.logger.New("old.round", -1, "old.seq", 0) 163 } else { 164 oldLogger = c.logger.New("old.round", c.current.Round().Uint64(), "old.sequence", c.current.Sequence().Uint64(), "old.state", c.state.String(), "old.proposer", c.valSet.GetProposer()) 165 } 166 167 // Create next view 168 var newView *istanbul.View 169 if roundChange { 170 newView = &istanbul.View{ 171 Sequence: new(big.Int).Set(c.current.Sequence()), 172 Round: new(big.Int).Set(round), 173 } 174 } else { 175 newView = &istanbul.View{ 176 Sequence: new(big.Int).Add(lastProposal.Number(), common.Big1), 177 Round: new(big.Int), 178 } 179 c.valSet = c.backend.Validators(lastProposal) 180 } 181 182 // New snapshot for new round 183 c.updateRoundState(newView, c.valSet, roundChange) 184 185 // Calculate new proposer 186 c.valSet.CalcProposer(lastProposer, newView.Round.Uint64()) 187 c.setState(StateAcceptRequest) 188 189 if round.Cmp(c.current.Round()) > 0 { 190 roundMeter.Mark(new(big.Int).Sub(round, c.current.Round()).Int64()) 191 } 192 193 // Update RoundChangeSet by deleting older round messages 194 if round.Uint64() == 0 { 195 c.QBFTPreparedPrepares = nil 196 c.roundChangeSet = newRoundChangeSet(c.valSet) 197 } else { 198 // Clear earlier round messages 199 c.roundChangeSet.ClearLowerThan(round) 200 } 201 c.roundChangeSet.NewRound(round) 202 203 if round.Uint64() > 0 { 204 c.newRoundChangeTimer() 205 } 206 207 oldLogger.Info("IBFT: start new round", "next.round", newView.Round, "next.seq", newView.Sequence, "next.proposer", c.valSet.GetProposer(), "next.valSet", c.valSet.List(), "next.size", c.valSet.Size(), "next.IsProposer", c.IsProposer()) 208 } 209 210 // updateRoundState updates round state by checking if locking block is necessary 211 func (c *core) updateRoundState(view *istanbul.View, validatorSet istanbul.ValidatorSet, roundChange bool) { 212 if roundChange && c.current != nil { 213 c.current = newRoundState(view, validatorSet, c.current.Preprepare, c.current.preparedRound, c.current.preparedBlock, c.current.pendingRequest, c.backend.HasBadProposal) 214 } else { 215 c.current = newRoundState(view, validatorSet, nil, nil, nil, nil, c.backend.HasBadProposal) 216 } 217 } 218 219 func (c *core) setState(state State) { 220 if c.state != state { 221 oldState := c.state 222 c.state = state 223 c.currentLogger(false, nil).Info("IBFT: changed state", "old.state", oldState.String(), "new.state", state.String()) 224 } 225 if state == StateAcceptRequest { 226 c.processPendingRequests() 227 } 228 229 // each time we change state, we process backlog for possible message that are 230 // now ready 231 c.processBacklog() 232 } 233 234 func (c *core) Address() common.Address { 235 return c.address 236 } 237 238 func (c *core) stopFuturePreprepareTimer() { 239 if c.futurePreprepareTimer != nil { 240 c.futurePreprepareTimer.Stop() 241 } 242 } 243 244 func (c *core) stopTimer() { 245 c.stopFuturePreprepareTimer() 246 if c.roundChangeTimer != nil { 247 c.roundChangeTimer.Stop() 248 } 249 } 250 251 func (c *core) newRoundChangeTimer() { 252 c.stopTimer() 253 254 // set timeout based on the round number 255 baseTimeout := time.Duration(c.config.RequestTimeout) * time.Millisecond 256 round := c.current.Round().Uint64() 257 258 timeout := baseTimeout * time.Duration(math.Pow(2, float64(round))) 259 260 c.currentLogger(true, nil).Trace("IBFT: start new ROUND-CHANGE timer", "timeout", timeout.Seconds()) 261 c.roundChangeTimer = time.AfterFunc(timeout, func() { 262 c.sendEvent(timeoutEvent{}) 263 }) 264 } 265 266 func (c *core) checkValidatorSignature(data []byte, sig []byte) (common.Address, error) { 267 return istanbul.CheckValidatorSignature(c.valSet, data, sig) 268 } 269 270 func (c *core) QuorumSize() int { 271 return int(math.Ceil(float64(2*c.valSet.Size()) / 3)) 272 } 273 274 // PrepareCommittedSeal returns a committed seal for the given header and takes current round under consideration 275 func PrepareCommittedSeal(header *types.Header, round uint32) []byte { 276 h := types.CopyHeader(header) 277 return h.QBFTHashWithRoundNumber(round).Bytes() 278 }