github.com/klaytn/klaytn@v1.10.2/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 } else { 197 // TODO-Klaytn never happen, but if proposal is nil, mining is not working. 198 logger.Error("istanbul.core current.Proposal is NULL") 199 c.current.UnlockHash() // Unlock block when insertion fails 200 c.sendNextRoundChange("commit failure. proposal is nil") 201 return 202 } 203 } 204 205 // startNewRound starts a new round. if round equals to 0, it means to starts a new sequence 206 func (c *core) startNewRound(round *big.Int) { 207 var logger log.Logger 208 if c.current == nil { 209 logger = c.logger.NewWith("old_round", -1, "old_seq", 0) 210 } else { 211 logger = c.logger.NewWith("old_round", c.current.Round(), "old_seq", c.current.Sequence()) 212 } 213 214 roundChange := false 215 // Try to get last proposal 216 lastProposal, lastProposer := c.backend.LastProposal() 217 //if c.valSet != nil && c.valSet.IsSubSet() { 218 // c.current = nil 219 //} else { 220 if c.current == nil { 221 logger.Trace("Start to the initial round") 222 } else if lastProposal.Number().Cmp(c.current.Sequence()) >= 0 { 223 diff := new(big.Int).Sub(lastProposal.Number(), c.current.Sequence()) 224 c.sequenceMeter.Mark(new(big.Int).Add(diff, common.Big1).Int64()) 225 226 if !c.consensusTimestamp.IsZero() { 227 c.consensusTimeGauge.Update(int64(time.Since(c.consensusTimestamp))) 228 c.consensusTimestamp = time.Time{} 229 } 230 logger.Trace("Catch up latest proposal", "number", lastProposal.Number().Uint64(), "hash", lastProposal.Hash()) 231 } else if lastProposal.Number().Cmp(big.NewInt(c.current.Sequence().Int64()-1)) == 0 { 232 if round.Cmp(common.Big0) == 0 { 233 // same seq and round, don't need to start new round 234 return 235 } else if round.Cmp(c.current.Round()) < 0 { 236 logger.Warn("New round should not be smaller than current round", "seq", lastProposal.Number().Int64(), "new_round", round, "old_round", c.current.Round()) 237 return 238 } 239 roundChange = true 240 } else { 241 logger.Warn("New sequence should be larger than current sequence", "new_seq", lastProposal.Number().Int64()) 242 return 243 } 244 //} 245 246 var newView *istanbul.View 247 if roundChange { 248 newView = &istanbul.View{ 249 Sequence: new(big.Int).Set(c.current.Sequence()), 250 Round: new(big.Int).Set(round), 251 } 252 } else { 253 newView = &istanbul.View{ 254 Sequence: new(big.Int).Add(lastProposal.Number(), common.Big1), 255 Round: new(big.Int), 256 } 257 c.valSet = c.backend.Validators(lastProposal) 258 259 councilSize := int64(c.valSet.Size()) 260 committeeSize := int64(c.valSet.SubGroupSize()) 261 if committeeSize > councilSize { 262 committeeSize = councilSize 263 } 264 c.councilSizeGauge.Update(councilSize) 265 c.committeeSizeGauge.Update(committeeSize) 266 } 267 c.backend.SetCurrentView(newView) 268 269 // Update logger 270 logger = logger.NewWith("old_proposer", c.valSet.GetProposer()) 271 // Clear invalid ROUND CHANGE messages 272 c.roundChangeSet = newRoundChangeSet(c.valSet) 273 // New snapshot for new round 274 c.updateRoundState(newView, c.valSet, roundChange) 275 // Calculate new proposer 276 c.valSet.CalcProposer(lastProposer, newView.Round.Uint64()) 277 c.waitingForRoundChange = false 278 c.setState(StateAcceptRequest) 279 if roundChange && c.isProposer() && c.current != nil { 280 // If it is locked, propose the old proposal 281 // If we have pending request, propose pending request 282 if c.current.IsHashLocked() { 283 r := &istanbul.Request{ 284 Proposal: c.current.Proposal(), // c.current.Proposal would be the locked proposal by previous proposer, see updateRoundState 285 } 286 c.sendPreprepare(r) 287 } else if c.current.pendingRequest != nil { 288 c.sendPreprepare(c.current.pendingRequest) 289 } 290 } 291 c.newRoundChangeTimer() 292 293 logger.Debug("New round", "new_round", newView.Round, "new_seq", newView.Sequence, "new_proposer", c.valSet.GetProposer(), "isProposer", c.isProposer()) 294 logger.Trace("New round", "new_round", newView.Round, "new_seq", newView.Sequence, "size", c.valSet.Size(), "valSet", c.valSet.List()) 295 } 296 297 func (c *core) catchUpRound(view *istanbul.View) { 298 logger := c.logger.NewWith("old_round", c.current.Round(), "old_seq", c.current.Sequence(), "old_proposer", c.valSet.GetProposer()) 299 300 if view.Round.Cmp(c.current.Round()) > 0 { 301 c.roundMeter.Mark(new(big.Int).Sub(view.Round, c.current.Round()).Int64()) 302 } 303 c.waitingForRoundChange = true 304 305 // Need to keep block locked for round catching up 306 c.updateRoundState(view, c.valSet, true) 307 c.roundChangeSet.Clear(view.Round) 308 309 c.newRoundChangeTimer() 310 logger.Warn("[RC] Catch up round", "new_round", view.Round, "new_seq", view.Sequence, "new_proposer", c.valSet.GetProposer()) 311 } 312 313 // updateRoundState updates round state by checking if locking block is necessary 314 func (c *core) updateRoundState(view *istanbul.View, validatorSet istanbul.ValidatorSet, roundChange bool) { 315 // Lock only if both roundChange is true and it is locked 316 if roundChange && c.current != nil { 317 if c.current.IsHashLocked() { 318 c.current = newRoundState(view, validatorSet, c.current.GetLockedHash(), c.current.Preprepare, c.current.pendingRequest, c.backend.HasBadProposal) 319 } else { 320 c.current = newRoundState(view, validatorSet, common.Hash{}, nil, c.current.pendingRequest, c.backend.HasBadProposal) 321 } 322 } else { 323 c.current = newRoundState(view, validatorSet, common.Hash{}, nil, nil, c.backend.HasBadProposal) 324 } 325 c.currentRoundGauge.Update(c.current.round.Int64()) 326 if c.current.IsHashLocked() { 327 c.hashLockGauge.Update(1) 328 } else { 329 c.hashLockGauge.Update(0) 330 } 331 } 332 333 func (c *core) setState(state State) { 334 if c.state != state { 335 c.state = state 336 } 337 if state == StateAcceptRequest { 338 c.processPendingRequests() 339 } 340 c.processBacklog() 341 } 342 343 func (c *core) Address() common.Address { 344 return c.address 345 } 346 347 func (c *core) stopFuturePreprepareTimer() { 348 if c.futurePreprepareTimer != nil { 349 c.futurePreprepareTimer.Stop() 350 } 351 } 352 353 func (c *core) stopTimer() { 354 c.stopFuturePreprepareTimer() 355 356 if c.roundChangeTimer.Load() != nil { 357 c.roundChangeTimer.Load().(*time.Timer).Stop() 358 } 359 } 360 361 func (c *core) newRoundChangeTimer() { 362 c.stopTimer() 363 364 // TODO-Klaytn-Istanbul: Replace &istanbul.DefaultConfig.Timeout to c.config.Timeout 365 // set timeout based on the round number 366 timeout := time.Duration(atomic.LoadUint64(&istanbul.DefaultConfig.Timeout)) * time.Millisecond 367 round := c.current.Round().Uint64() 368 if round > 0 { 369 timeout += time.Duration(math.Pow(2, float64(round))) * time.Second 370 } 371 372 current := c.current 373 proposer := c.valSet.GetProposer() 374 375 c.roundChangeTimer.Store(time.AfterFunc(timeout, func() { 376 var loc, proposerStr string 377 378 if round == 0 { 379 loc = "startNewRound" 380 } else { 381 loc = "catchUpRound" 382 } 383 if proposer == nil { 384 proposerStr = "" 385 } else { 386 proposerStr = proposer.String() 387 } 388 389 if c.backend.NodeType() == common.CONSENSUSNODE { 390 // Write log messages for validator activities analysis 391 preparesSize := current.Prepares.Size() 392 commitsSize := current.Commits.Size() 393 logger.Warn("[RC] timeoutEvent Sent!", "set by", loc, "sequence", 394 current.sequence, "round", current.round, "proposer", proposerStr, "preprepare is nil?", 395 current.Preprepare == nil, "len(prepares)", preparesSize, "len(commits)", commitsSize) 396 397 if preparesSize > 0 { 398 logger.Warn("[RC] Prepares:", "messages", current.Prepares.GetMessages()) 399 } 400 if commitsSize > 0 { 401 logger.Warn("[RC] Commits:", "messages", current.Commits.GetMessages()) 402 } 403 } 404 405 c.sendEvent(timeoutEvent{&istanbul.View{ 406 Sequence: current.sequence, 407 Round: new(big.Int).Add(current.round, common.Big1), 408 }}) 409 })) 410 411 logger.Debug("New RoundChangeTimer Set", "seq", c.current.Sequence(), "round", round, "timeout", timeout) 412 } 413 414 func (c *core) checkValidatorSignature(data []byte, sig []byte) (common.Address, error) { 415 return istanbul.CheckValidatorSignature(c.valSet, data, sig) 416 } 417 418 // PrepareCommittedSeal returns a committed seal for the given hash 419 func PrepareCommittedSeal(hash common.Hash) []byte { 420 var buf bytes.Buffer 421 buf.Write(hash.Bytes()) 422 buf.Write([]byte{byte(msgCommit)}) 423 return buf.Bytes() 424 } 425 426 // Minimum required number of consensus messages to proceed 427 func requiredMessageCount(valSet istanbul.ValidatorSet) int { 428 var size uint64 429 if valSet.IsSubSet() { 430 size = valSet.SubGroupSize() 431 } else { 432 size = valSet.Size() 433 } 434 switch size { 435 // in the certain cases we must receive the messages from all consensus nodes to ensure finality... 436 case 1, 2, 3: 437 return int(size) 438 case 6: 439 return 4 // when the number of valSet is 6 and return value is 2*F+1, the return value(int 3) is not safe. It should return 4 or more. 440 default: 441 return 2*valSet.F() + 1 442 } 443 }