github.com/ConsenSys/Quorum@v20.10.0+incompatible/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 "bytes" 21 "math" 22 "math/big" 23 "sync" 24 "time" 25 26 "github.com/ethereum/go-ethereum/common" 27 "github.com/ethereum/go-ethereum/consensus/istanbul" 28 "github.com/ethereum/go-ethereum/core/types" 29 "github.com/ethereum/go-ethereum/event" 30 "github.com/ethereum/go-ethereum/log" 31 metrics "github.com/ethereum/go-ethereum/metrics" 32 "gopkg.in/karalabe/cookiejar.v2/collections/prque" 33 ) 34 35 // New creates an Istanbul consensus core 36 func New(backend istanbul.Backend, config *istanbul.Config) Engine { 37 r := metrics.NewRegistry() 38 c := &core{ 39 config: config, 40 address: backend.Address(), 41 state: StateAcceptRequest, 42 handlerWg: new(sync.WaitGroup), 43 logger: log.New("address", backend.Address()), 44 backend: backend, 45 backlogs: make(map[common.Address]*prque.Prque), 46 backlogsMu: new(sync.Mutex), 47 pendingRequests: prque.New(), 48 pendingRequestsMu: new(sync.Mutex), 49 consensusTimestamp: time.Time{}, 50 roundMeter: metrics.NewMeter(), 51 sequenceMeter: metrics.NewMeter(), 52 consensusTimer: metrics.NewTimer(), 53 } 54 55 r.Register("consensus/istanbul/core/round", c.roundMeter) 56 r.Register("consensus/istanbul/core/sequence", c.sequenceMeter) 57 r.Register("consensus/istanbul/core/consensus", c.consensusTimer) 58 59 c.validateFn = c.checkValidatorSignature 60 return c 61 } 62 63 // ---------------------------------------------------------------------------- 64 65 type core struct { 66 config *istanbul.Config 67 address common.Address 68 state State 69 logger log.Logger 70 71 backend istanbul.Backend 72 events *event.TypeMuxSubscription 73 finalCommittedSub *event.TypeMuxSubscription 74 timeoutSub *event.TypeMuxSubscription 75 futurePreprepareTimer *time.Timer 76 77 valSet istanbul.ValidatorSet 78 waitingForRoundChange bool 79 validateFn func([]byte, []byte) (common.Address, error) 80 81 backlogs map[common.Address]*prque.Prque 82 backlogsMu *sync.Mutex 83 84 current *roundState 85 handlerWg *sync.WaitGroup 86 87 roundChangeSet *roundChangeSet 88 roundChangeTimer *time.Timer 89 90 pendingRequests *prque.Prque 91 pendingRequestsMu *sync.Mutex 92 93 consensusTimestamp time.Time 94 // the meter to record the round change rate 95 roundMeter metrics.Meter 96 // the meter to record the sequence update rate 97 sequenceMeter metrics.Meter 98 // the timer to record consensus duration (from accepting a preprepare to final committed stage) 99 consensusTimer metrics.Timer 100 } 101 102 func (c *core) finalizeMessage(msg *message) ([]byte, error) { 103 var err error 104 // Add sender address 105 msg.Address = c.Address() 106 107 // Add proof of consensus 108 msg.CommittedSeal = []byte{} 109 // Assign the CommittedSeal if it's a COMMIT message and proposal is not nil 110 if msg.Code == msgCommit && c.current.Proposal() != nil { 111 seal := PrepareCommittedSeal(c.current.Proposal().Hash()) 112 msg.CommittedSeal, err = c.backend.Sign(seal) 113 if err != nil { 114 return nil, err 115 } 116 } 117 118 // Sign message 119 data, err := msg.PayloadNoSig() 120 if err != nil { 121 return nil, err 122 } 123 msg.Signature, err = c.backend.Sign(data) 124 if err != nil { 125 return nil, err 126 } 127 128 // Convert to payload 129 payload, err := msg.Payload() 130 if err != nil { 131 return nil, err 132 } 133 134 return payload, nil 135 } 136 137 func (c *core) broadcast(msg *message) { 138 logger := c.logger.New("state", c.state) 139 140 payload, err := c.finalizeMessage(msg) 141 if err != nil { 142 logger.Error("Failed to finalize message", "msg", msg, "err", err) 143 return 144 } 145 146 // Broadcast payload 147 if err = c.backend.Broadcast(c.valSet, payload); err != nil { 148 logger.Error("Failed to broadcast message", "msg", msg, "err", err) 149 return 150 } 151 } 152 153 func (c *core) currentView() *istanbul.View { 154 return &istanbul.View{ 155 Sequence: new(big.Int).Set(c.current.Sequence()), 156 Round: new(big.Int).Set(c.current.Round()), 157 } 158 } 159 160 func (c *core) IsProposer() bool { 161 v := c.valSet 162 if v == nil { 163 return false 164 } 165 return v.IsProposer(c.backend.Address()) 166 } 167 168 func (c *core) IsCurrentProposal(blockHash common.Hash) bool { 169 return c.current != nil && c.current.pendingRequest != nil && c.current.pendingRequest.Proposal.Hash() == blockHash 170 } 171 172 func (c *core) commit() { 173 c.setState(StateCommitted) 174 175 proposal := c.current.Proposal() 176 if proposal != nil { 177 committedSeals := make([][]byte, c.current.Commits.Size()) 178 for i, v := range c.current.Commits.Values() { 179 committedSeals[i] = make([]byte, types.IstanbulExtraSeal) 180 copy(committedSeals[i][:], v.CommittedSeal[:]) 181 } 182 183 if err := c.backend.Commit(proposal, committedSeals); err != nil { 184 c.current.UnlockHash() //Unlock block when insertion fails 185 c.sendNextRoundChange() 186 return 187 } 188 } 189 } 190 191 // startNewRound starts a new round. if round equals to 0, it means to starts a new sequence 192 func (c *core) startNewRound(round *big.Int) { 193 var logger log.Logger 194 if c.current == nil { 195 logger = c.logger.New("old_round", -1, "old_seq", 0) 196 } else { 197 logger = c.logger.New("old_round", c.current.Round(), "old_seq", c.current.Sequence()) 198 } 199 200 roundChange := false 201 // Try to get last proposal 202 lastProposal, lastProposer := c.backend.LastProposal() 203 if c.current == nil { 204 logger.Trace("Start to the initial round") 205 } else if lastProposal.Number().Cmp(c.current.Sequence()) >= 0 { 206 diff := new(big.Int).Sub(lastProposal.Number(), c.current.Sequence()) 207 c.sequenceMeter.Mark(new(big.Int).Add(diff, common.Big1).Int64()) 208 209 if !c.consensusTimestamp.IsZero() { 210 c.consensusTimer.UpdateSince(c.consensusTimestamp) 211 c.consensusTimestamp = time.Time{} 212 } 213 logger.Trace("Catch up latest proposal", "number", lastProposal.Number().Uint64(), "hash", lastProposal.Hash()) 214 } else if lastProposal.Number().Cmp(big.NewInt(c.current.Sequence().Int64()-1)) == 0 { 215 if round.Cmp(common.Big0) == 0 { 216 // same seq and round, don't need to start new round 217 return 218 } else if round.Cmp(c.current.Round()) < 0 { 219 logger.Warn("New round should not be smaller than current round", "seq", lastProposal.Number().Int64(), "new_round", round, "old_round", c.current.Round()) 220 return 221 } 222 roundChange = true 223 } else { 224 logger.Warn("New sequence should be larger than current sequence", "new_seq", lastProposal.Number().Int64()) 225 return 226 } 227 228 var newView *istanbul.View 229 if roundChange { 230 newView = &istanbul.View{ 231 Sequence: new(big.Int).Set(c.current.Sequence()), 232 Round: new(big.Int).Set(round), 233 } 234 } else { 235 newView = &istanbul.View{ 236 Sequence: new(big.Int).Add(lastProposal.Number(), common.Big1), 237 Round: new(big.Int), 238 } 239 c.valSet = c.backend.Validators(lastProposal) 240 } 241 242 // Update logger 243 logger = logger.New("old_proposer", c.valSet.GetProposer()) 244 // Clear invalid ROUND CHANGE messages 245 c.roundChangeSet = newRoundChangeSet(c.valSet) 246 // New snapshot for new round 247 c.updateRoundState(newView, c.valSet, roundChange) 248 // Calculate new proposer 249 c.valSet.CalcProposer(lastProposer, newView.Round.Uint64()) 250 c.waitingForRoundChange = false 251 c.setState(StateAcceptRequest) 252 if roundChange && c.IsProposer() && c.current != nil { 253 // If it is locked, propose the old proposal 254 // If we have pending request, propose pending request 255 if c.current.IsHashLocked() { 256 r := &istanbul.Request{ 257 Proposal: c.current.Proposal(), //c.current.Proposal would be the locked proposal by previous proposer, see updateRoundState 258 } 259 c.sendPreprepare(r) 260 } else if c.current.pendingRequest != nil { 261 c.sendPreprepare(c.current.pendingRequest) 262 } 263 } 264 c.newRoundChangeTimer() 265 266 logger.Debug("New round", "new_round", newView.Round, "new_seq", newView.Sequence, "new_proposer", c.valSet.GetProposer(), "valSet", c.valSet.List(), "size", c.valSet.Size(), "IsProposer", c.IsProposer()) 267 } 268 269 func (c *core) catchUpRound(view *istanbul.View) { 270 logger := c.logger.New("old_round", c.current.Round(), "old_seq", c.current.Sequence(), "old_proposer", c.valSet.GetProposer()) 271 272 if view.Round.Cmp(c.current.Round()) > 0 { 273 c.roundMeter.Mark(new(big.Int).Sub(view.Round, c.current.Round()).Int64()) 274 } 275 c.waitingForRoundChange = true 276 277 // Need to keep block locked for round catching up 278 c.updateRoundState(view, c.valSet, true) 279 c.roundChangeSet.Clear(view.Round) 280 c.newRoundChangeTimer() 281 282 logger.Trace("Catch up round", "new_round", view.Round, "new_seq", view.Sequence, "new_proposer", c.valSet) 283 } 284 285 // updateRoundState updates round state by checking if locking block is necessary 286 func (c *core) updateRoundState(view *istanbul.View, validatorSet istanbul.ValidatorSet, roundChange bool) { 287 // Lock only if both roundChange is true and it is locked 288 if roundChange && c.current != nil { 289 if c.current.IsHashLocked() { 290 c.current = newRoundState(view, validatorSet, c.current.GetLockedHash(), c.current.Preprepare, c.current.pendingRequest, c.backend.HasBadProposal) 291 } else { 292 c.current = newRoundState(view, validatorSet, common.Hash{}, nil, c.current.pendingRequest, c.backend.HasBadProposal) 293 } 294 } else { 295 c.current = newRoundState(view, validatorSet, common.Hash{}, nil, nil, c.backend.HasBadProposal) 296 } 297 } 298 299 func (c *core) setState(state State) { 300 if c.state != state { 301 c.state = state 302 } 303 if state == StateAcceptRequest { 304 c.processPendingRequests() 305 } 306 c.processBacklog() 307 } 308 309 func (c *core) Address() common.Address { 310 return c.address 311 } 312 313 func (c *core) stopFuturePreprepareTimer() { 314 if c.futurePreprepareTimer != nil { 315 c.futurePreprepareTimer.Stop() 316 } 317 } 318 319 func (c *core) stopTimer() { 320 c.stopFuturePreprepareTimer() 321 if c.roundChangeTimer != nil { 322 c.roundChangeTimer.Stop() 323 } 324 } 325 326 func (c *core) newRoundChangeTimer() { 327 c.stopTimer() 328 329 // set timeout based on the round number 330 timeout := time.Duration(c.config.RequestTimeout) * time.Millisecond 331 round := c.current.Round().Uint64() 332 if round > 0 { 333 timeout += time.Duration(math.Pow(2, float64(round))) * time.Second 334 } 335 c.roundChangeTimer = time.AfterFunc(timeout, func() { 336 c.sendEvent(timeoutEvent{}) 337 }) 338 } 339 340 func (c *core) checkValidatorSignature(data []byte, sig []byte) (common.Address, error) { 341 return istanbul.CheckValidatorSignature(c.valSet, data, sig) 342 } 343 344 func (c *core) QuorumSize() int { 345 if c.config.Ceil2Nby3Block == nil || (c.current != nil && c.current.sequence.Cmp(c.config.Ceil2Nby3Block) < 0) { 346 c.logger.Trace("Confirmation Formula used 2F+ 1") 347 return (2 * c.valSet.F()) + 1 348 } 349 c.logger.Trace("Confirmation Formula used ceil(2N/3)") 350 return int(math.Ceil(float64(2*c.valSet.Size()) / 3)) 351 } 352 353 // PrepareCommittedSeal returns a committed seal for the given hash 354 func PrepareCommittedSeal(hash common.Hash) []byte { 355 var buf bytes.Buffer 356 buf.Write(hash.Bytes()) 357 buf.Write([]byte{byte(msgCommit)}) 358 return buf.Bytes() 359 }