github.com/klaytn/klaytn@v1.12.1/consensus/istanbul/core/core.go (about) 1 // Modifications Copyright 2018 The klaytn Authors 2 // Copyright 2017 The go-ethereum Authors 3 // This file is part of the go-ethereum library. 4 // 5 // The go-ethereum library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // The go-ethereum library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public License 16 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 17 // 18 // This file is derived from quorum/consensus/istanbul/core/core.go (2018/06/04). 19 // Modified and improved for the klaytn development. 20 21 package core 22 23 import ( 24 "bytes" 25 "math" 26 "math/big" 27 "sync" 28 "sync/atomic" 29 "time" 30 31 "github.com/klaytn/klaytn/blockchain/types" 32 "github.com/klaytn/klaytn/common" 33 "github.com/klaytn/klaytn/common/prque" 34 "github.com/klaytn/klaytn/consensus/istanbul" 35 "github.com/klaytn/klaytn/event" 36 "github.com/klaytn/klaytn/log" 37 "github.com/rcrowley/go-metrics" 38 ) 39 40 var logger = log.NewModuleLogger(log.ConsensusIstanbulCore) 41 42 // New creates an Istanbul consensus core 43 func New(backend istanbul.Backend, config *istanbul.Config) Engine { 44 c := &core{ 45 config: config, 46 address: backend.Address(), 47 state: StateAcceptRequest, 48 handlerWg: new(sync.WaitGroup), 49 logger: logger.NewWith("address", backend.Address()), 50 backend: backend, 51 backlogs: make(map[common.Address]*prque.Prque), 52 backlogsMu: new(sync.Mutex), 53 pendingRequests: prque.New(), 54 pendingRequestsMu: new(sync.Mutex), 55 consensusTimestamp: time.Time{}, 56 57 roundMeter: metrics.NewRegisteredMeter("consensus/istanbul/core/round", nil), 58 currentRoundGauge: metrics.NewRegisteredGauge("consensus/istanbul/core/currentRound", nil), 59 sequenceMeter: metrics.NewRegisteredMeter("consensus/istanbul/core/sequence", nil), 60 consensusTimeGauge: metrics.NewRegisteredGauge("consensus/istanbul/core/timer", nil), 61 councilSizeGauge: metrics.NewRegisteredGauge("consensus/istanbul/core/councilSize", nil), 62 committeeSizeGauge: metrics.NewRegisteredGauge("consensus/istanbul/core/committeeSize", nil), 63 hashLockGauge: metrics.NewRegisteredGauge("consensus/istanbul/core/hashLock", nil), 64 } 65 c.validateFn = c.checkValidatorSignature 66 return c 67 } 68 69 // ---------------------------------------------------------------------------- 70 71 type core struct { 72 config *istanbul.Config 73 address common.Address 74 state State 75 logger log.Logger 76 77 backend istanbul.Backend 78 events *event.TypeMuxSubscription 79 finalCommittedSub *event.TypeMuxSubscription 80 timeoutSub *event.TypeMuxSubscription 81 futurePreprepareTimer *time.Timer 82 83 valSet istanbul.ValidatorSet 84 waitingForRoundChange bool 85 validateFn func([]byte, []byte) (common.Address, error) 86 87 backlogs map[common.Address]*prque.Prque 88 backlogsMu *sync.Mutex 89 90 current *roundState 91 handlerWg *sync.WaitGroup 92 93 roundChangeSet *roundChangeSet 94 roundChangeTimer atomic.Value //*time.Timer 95 pendingRequests *prque.Prque 96 pendingRequestsMu *sync.Mutex 97 98 consensusTimestamp time.Time 99 // the meter to record the round change rate 100 roundMeter metrics.Meter 101 // the gauge to record the current round 102 currentRoundGauge metrics.Gauge 103 // the meter to record the sequence update rate 104 sequenceMeter metrics.Meter 105 // the gauge to record consensus duration (from accepting a preprepare to final committed stage) 106 consensusTimeGauge metrics.Gauge 107 // the gauge to record hashLock status (1 if hash-locked. 0 otherwise) 108 hashLockGauge metrics.Gauge 109 110 councilSizeGauge metrics.Gauge 111 committeeSizeGauge metrics.Gauge 112 } 113 114 func (c *core) finalizeMessage(msg *message) ([]byte, error) { 115 var err error 116 // Add sender address 117 msg.Address = c.Address() 118 119 // Add proof of consensus 120 msg.CommittedSeal = []byte{} 121 // Assign the CommittedSeal if it's a COMMIT message and proposal is not nil 122 if msg.Code == msgCommit && c.current.Proposal() != nil { 123 seal := PrepareCommittedSeal(c.current.Proposal().Hash()) 124 msg.CommittedSeal, err = c.backend.Sign(seal) 125 if err != nil { 126 return nil, err 127 } 128 } 129 130 // Sign message 131 data, err := msg.PayloadNoSig() 132 if err != nil { 133 return nil, err 134 } 135 msg.Signature, err = c.backend.Sign(data) 136 if err != nil { 137 return nil, err 138 } 139 140 // Convert to payload 141 payload, err := msg.Payload() 142 if err != nil { 143 return nil, err 144 } 145 146 return payload, nil 147 } 148 149 func (c *core) broadcast(msg *message) { 150 logger := c.logger.NewWith("state", c.state) 151 152 payload, err := c.finalizeMessage(msg) 153 if err != nil { 154 logger.Error("Failed to finalize message", "msg", msg, "err", err) 155 return 156 } 157 158 // Broadcast payload 159 if err = c.backend.Broadcast(msg.Hash, c.valSet, payload); err != nil { 160 logger.Error("Failed to broadcast message", "msg", msg, "err", err) 161 return 162 } 163 } 164 165 func (c *core) currentView() *istanbul.View { 166 return &istanbul.View{ 167 Sequence: new(big.Int).Set(c.current.Sequence()), 168 Round: new(big.Int).Set(c.current.Round()), 169 } 170 } 171 172 func (c *core) isProposer() bool { 173 v := c.valSet 174 if v == nil { 175 return false 176 } 177 return v.IsProposer(c.backend.Address()) 178 } 179 180 func (c *core) commit() { 181 c.setState(StateCommitted) 182 183 proposal := c.current.Proposal() 184 if proposal != nil { 185 committedSeals := make([][]byte, c.current.Commits.Size()) 186 for i, v := range c.current.Commits.Values() { 187 committedSeals[i] = make([]byte, types.IstanbulExtraSeal) 188 copy(committedSeals[i][:], v.CommittedSeal[:]) 189 } 190 191 if err := c.backend.Commit(proposal, committedSeals); err != nil { 192 c.current.UnlockHash() // Unlock block when insertion fails 193 c.sendNextRoundChange("commit failure") 194 return 195 } 196 197 if vrank != nil { 198 vrank.HandleCommitted(proposal.Number()) 199 } 200 } else { 201 // TODO-Klaytn never happen, but if proposal is nil, mining is not working. 202 logger.Error("istanbul.core current.Proposal is NULL") 203 c.current.UnlockHash() // Unlock block when insertion fails 204 c.sendNextRoundChange("commit failure. proposal is nil") 205 return 206 } 207 } 208 209 // startNewRound starts a new round. if round equals to 0, it means to starts a new sequence 210 func (c *core) startNewRound(round *big.Int) { 211 var logger log.Logger 212 if c.current == nil { 213 logger = c.logger.NewWith("old_round", -1, "old_seq", 0) 214 } else { 215 logger = c.logger.NewWith("old_round", c.current.Round(), "old_seq", c.current.Sequence()) 216 } 217 218 roundChange := false 219 // Try to get last proposal 220 lastProposal, lastProposer := c.backend.LastProposal() 221 //if c.valSet != nil && c.valSet.IsSubSet() { 222 // c.current = nil 223 //} else { 224 if c.current == nil { 225 logger.Trace("Start to the initial round") 226 } else if lastProposal.Number().Cmp(c.current.Sequence()) >= 0 { 227 diff := new(big.Int).Sub(lastProposal.Number(), c.current.Sequence()) 228 c.sequenceMeter.Mark(new(big.Int).Add(diff, common.Big1).Int64()) 229 230 if !c.consensusTimestamp.IsZero() { 231 c.consensusTimeGauge.Update(int64(time.Since(c.consensusTimestamp))) 232 c.consensusTimestamp = time.Time{} 233 } 234 logger.Trace("Catch up latest proposal", "number", lastProposal.Number().Uint64(), "hash", lastProposal.Hash()) 235 } else if lastProposal.Number().Cmp(big.NewInt(c.current.Sequence().Int64()-1)) == 0 { 236 if round.Cmp(common.Big0) == 0 { 237 // same seq and round, don't need to start new round 238 return 239 } else if round.Cmp(c.current.Round()) < 0 { 240 logger.Warn("New round should not be smaller than current round", "seq", lastProposal.Number().Int64(), "new_round", round, "old_round", c.current.Round()) 241 return 242 } 243 roundChange = true 244 } else { 245 logger.Warn("New sequence should be larger than current sequence", "new_seq", lastProposal.Number().Int64()) 246 return 247 } 248 //} 249 250 var newView *istanbul.View 251 if roundChange { 252 newView = &istanbul.View{ 253 Sequence: new(big.Int).Set(c.current.Sequence()), 254 Round: new(big.Int).Set(round), 255 } 256 } else { 257 newView = &istanbul.View{ 258 Sequence: new(big.Int).Add(lastProposal.Number(), common.Big1), 259 Round: new(big.Int), 260 } 261 c.valSet = c.backend.Validators(lastProposal) 262 263 councilSize := int64(c.valSet.Size()) 264 committeeSize := int64(c.valSet.SubGroupSize()) 265 if committeeSize > councilSize { 266 committeeSize = councilSize 267 } 268 c.councilSizeGauge.Update(councilSize) 269 c.committeeSizeGauge.Update(committeeSize) 270 } 271 c.backend.SetCurrentView(newView) 272 273 // Update logger 274 logger = logger.NewWith("old_proposer", c.valSet.GetProposer()) 275 // Clear invalid ROUND CHANGE messages 276 c.roundChangeSet = newRoundChangeSet(c.valSet) 277 // New snapshot for new round 278 c.updateRoundState(newView, c.valSet, roundChange) 279 // Calculate new proposer 280 c.valSet.CalcProposer(lastProposer, newView.Round.Uint64()) 281 c.waitingForRoundChange = false 282 c.setState(StateAcceptRequest) 283 if roundChange && c.isProposer() && c.current != nil { 284 // If it is locked, propose the old proposal 285 // If we have pending request, propose pending request 286 if c.current.IsHashLocked() { 287 r := &istanbul.Request{ 288 Proposal: c.current.Proposal(), // c.current.Proposal would be the locked proposal by previous proposer, see updateRoundState 289 } 290 c.sendPreprepare(r) 291 } else if c.current.pendingRequest != nil { 292 c.sendPreprepare(c.current.pendingRequest) 293 } 294 } 295 c.newRoundChangeTimer() 296 297 logger.Debug("New round", "new_round", newView.Round, "new_seq", newView.Sequence, "new_proposer", c.valSet.GetProposer(), "isProposer", c.isProposer()) 298 logger.Trace("New round", "new_round", newView.Round, "new_seq", newView.Sequence, "size", c.valSet.Size(), "valSet", c.valSet.List()) 299 } 300 301 func (c *core) catchUpRound(view *istanbul.View) { 302 logger := c.logger.NewWith("old_round", c.current.Round(), "old_seq", c.current.Sequence(), "old_proposer", c.valSet.GetProposer()) 303 304 if view.Round.Cmp(c.current.Round()) > 0 { 305 c.roundMeter.Mark(new(big.Int).Sub(view.Round, c.current.Round()).Int64()) 306 } 307 c.waitingForRoundChange = true 308 309 // Need to keep block locked for round catching up 310 c.updateRoundState(view, c.valSet, true) 311 c.roundChangeSet.Clear(view.Round) 312 313 c.newRoundChangeTimer() 314 logger.Warn("[RC] Catch up round", "new_round", view.Round, "new_seq", view.Sequence, "new_proposer", c.valSet.GetProposer()) 315 } 316 317 // updateRoundState updates round state by checking if locking block is necessary 318 func (c *core) updateRoundState(view *istanbul.View, validatorSet istanbul.ValidatorSet, roundChange bool) { 319 // Lock only if both roundChange is true and it is locked 320 if roundChange && c.current != nil { 321 if c.current.IsHashLocked() { 322 c.current = newRoundState(view, validatorSet, c.current.GetLockedHash(), c.current.Preprepare, c.current.pendingRequest, c.backend.HasBadProposal) 323 } else { 324 c.current = newRoundState(view, validatorSet, common.Hash{}, nil, c.current.pendingRequest, c.backend.HasBadProposal) 325 } 326 } else { 327 c.current = newRoundState(view, validatorSet, common.Hash{}, nil, nil, c.backend.HasBadProposal) 328 } 329 c.currentRoundGauge.Update(c.current.round.Int64()) 330 if c.current.IsHashLocked() { 331 c.hashLockGauge.Update(1) 332 } else { 333 c.hashLockGauge.Update(0) 334 } 335 } 336 337 func (c *core) setState(state State) { 338 if c.state != state { 339 c.state = state 340 } 341 if state == StateAcceptRequest { 342 c.processPendingRequests() 343 } 344 c.processBacklog() 345 } 346 347 func (c *core) Address() common.Address { 348 return c.address 349 } 350 351 func (c *core) stopFuturePreprepareTimer() { 352 if c.futurePreprepareTimer != nil { 353 c.futurePreprepareTimer.Stop() 354 } 355 } 356 357 func (c *core) stopTimer() { 358 c.stopFuturePreprepareTimer() 359 360 if c.roundChangeTimer.Load() != nil { 361 c.roundChangeTimer.Load().(*time.Timer).Stop() 362 } 363 } 364 365 func (c *core) newRoundChangeTimer() { 366 c.stopTimer() 367 368 // TODO-Klaytn-Istanbul: Replace &istanbul.DefaultConfig.Timeout to c.config.Timeout 369 // set timeout based on the round number 370 timeout := time.Duration(atomic.LoadUint64(&istanbul.DefaultConfig.Timeout)) * time.Millisecond 371 round := c.current.Round().Uint64() 372 if round > 0 { 373 timeout += time.Duration(math.Pow(2, float64(round))) * time.Second 374 } 375 376 current := c.current 377 proposer := c.valSet.GetProposer() 378 379 c.roundChangeTimer.Store(time.AfterFunc(timeout, func() { 380 var loc, proposerStr string 381 382 if round == 0 { 383 loc = "startNewRound" 384 } else { 385 loc = "catchUpRound" 386 } 387 if proposer == nil { 388 proposerStr = "" 389 } else { 390 proposerStr = proposer.String() 391 } 392 393 if c.backend.NodeType() == common.CONSENSUSNODE { 394 // Write log messages for validator activities analysis 395 preparesSize := current.Prepares.Size() 396 commitsSize := current.Commits.Size() 397 logger.Warn("[RC] timeoutEvent Sent!", "set by", loc, "sequence", 398 current.sequence, "round", current.round, "proposer", proposerStr, "preprepare is nil?", 399 current.Preprepare == nil, "len(prepares)", preparesSize, "len(commits)", commitsSize) 400 401 if preparesSize > 0 { 402 logger.Warn("[RC] Prepares:", "messages", current.Prepares.GetMessages()) 403 } 404 if commitsSize > 0 { 405 logger.Warn("[RC] Commits:", "messages", current.Commits.GetMessages()) 406 } 407 } 408 409 c.sendEvent(timeoutEvent{&istanbul.View{ 410 Sequence: current.sequence, 411 Round: new(big.Int).Add(current.round, common.Big1), 412 }}) 413 })) 414 415 logger.Debug("New RoundChangeTimer Set", "seq", c.current.Sequence(), "round", round, "timeout", timeout) 416 } 417 418 func (c *core) checkValidatorSignature(data []byte, sig []byte) (common.Address, error) { 419 return istanbul.CheckValidatorSignature(c.valSet, data, sig) 420 } 421 422 // PrepareCommittedSeal returns a committed seal for the given hash 423 func PrepareCommittedSeal(hash common.Hash) []byte { 424 var buf bytes.Buffer 425 buf.Write(hash.Bytes()) 426 buf.Write([]byte{byte(msgCommit)}) 427 return buf.Bytes() 428 } 429 430 // Minimum required number of consensus messages to proceed 431 func RequiredMessageCount(valSet istanbul.ValidatorSet) int { 432 var size uint64 433 if valSet.IsSubSet() { 434 size = valSet.SubGroupSize() 435 } else { 436 size = valSet.Size() 437 } 438 // For less than 4 validators, quorum size equals validator count. 439 if size < 4 { 440 return int(size) 441 } 442 // Adopted QBFT quorum implementation 443 // https://github.com/Consensys/quorum/blob/master/consensus/istanbul/qbft/core/core.go#L312 444 return int(math.Ceil(float64(2*size) / 3)) 445 }