github.com/klaytn/klaytn@v1.10.2/consensus/istanbul/core/roundchange.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/roundchange.go (2018/06/04). 19 // Modified and improved for the klaytn development. 20 21 package core 22 23 import ( 24 "math/big" 25 "sync" 26 27 "github.com/klaytn/klaytn/common" 28 "github.com/klaytn/klaytn/consensus/istanbul" 29 ) 30 31 const ( 32 // If the number of validators is 6 or less, the chain could be forked by round change if the required minimum consensus message is "2f+1". 33 // So, the exceptional case such as number of validator is 6, gather more messages than "2f+1". See requiredMessageCount for more specific information. 34 exceptionalValidatorsNumber = 6 35 ) 36 37 // sendNextRoundChange sends the ROUND CHANGE message with current round + 1 38 func (c *core) sendNextRoundChange(loc string) { 39 if c.backend.NodeType() != common.CONSENSUSNODE { 40 return 41 } 42 logger.Warn("[RC] sendNextRoundChange happened", "where", loc) 43 c.sendRoundChange(new(big.Int).Add(c.currentView().Round, common.Big1)) 44 } 45 46 // sendRoundChange sends the ROUND CHANGE message with the given round 47 func (c *core) sendRoundChange(round *big.Int) { 48 logger := c.logger.NewWith("state", c.state) 49 50 cv := c.currentView() 51 if cv.Round.Cmp(round) >= 0 { 52 logger.Warn("[RC] Skip sending out the round change message", "current round", cv.Round, 53 "target round", round) 54 return 55 } 56 57 logger.Warn("[RC] Prepare messages received before catchUpRound", 58 "len(prepares)", c.current.Prepares.Size(), "messages", c.current.Prepares.GetMessages()) 59 logger.Warn("[RC] Commit messages received before catchUpRound", 60 "len(commits)", c.current.Commits.Size(), "messages", c.current.Commits.GetMessages()) 61 62 c.catchUpRound(&istanbul.View{ 63 // The round number we'd like to transfer to. 64 Round: new(big.Int).Set(round), 65 Sequence: new(big.Int).Set(cv.Sequence), 66 }) 67 68 lastProposal, _ := c.backend.LastProposal() 69 70 // Now we have the new round number and sequence number 71 cv = c.currentView() 72 rc := &istanbul.Subject{ 73 View: cv, 74 Digest: common.Hash{}, 75 PrevHash: lastProposal.Hash(), 76 } 77 78 payload, err := Encode(rc) 79 if err != nil { 80 logger.Error("Failed to encode ROUND CHANGE", "rc", rc, "err", err) 81 return 82 } 83 84 c.broadcast(&message{ 85 Hash: rc.PrevHash, 86 Code: msgRoundChange, 87 Msg: payload, 88 }) 89 } 90 91 func (c *core) handleRoundChange(msg *message, src istanbul.Validator) error { 92 logger := c.logger.NewWith("state", c.state, "from", src.Address().Hex()) 93 94 // Decode ROUND CHANGE message 95 var rc *istanbul.Subject 96 if err := msg.Decode(&rc); err != nil { 97 logger.Error("Failed to decode message", "code", msg.Code, "err", err) 98 return errInvalidMessage 99 } 100 101 // TODO-Klaytn-Istanbul: establish round change messaging policy and then apply it 102 //if !c.valSet.CheckInSubList(msg.Hash, rc.View, src.Address()) { 103 // return errNotFromCommittee 104 //} 105 106 if err := c.checkMessage(msgRoundChange, rc.View); err != nil { 107 return err 108 } 109 110 cv := c.currentView() 111 roundView := rc.View 112 113 // Add the ROUND CHANGE message to its message set and return how many 114 // messages we've got with the same round number and sequence number. 115 num, err := c.roundChangeSet.Add(roundView.Round, msg) 116 if err != nil { 117 logger.Warn("Failed to add round change message", "from", src, "msg", msg, "err", err) 118 return err 119 } 120 121 var numCatchUp, numStartNewRound int 122 if c.valSet.Size() <= exceptionalValidatorsNumber { 123 n := requiredMessageCount(c.valSet) 124 // N ROUND CHANGE messages can start new round. 125 numStartNewRound = n 126 // N - 1 ROUND CHANGE messages can catch up the round. 127 numCatchUp = n - 1 128 } else { 129 f := int(c.valSet.F()) 130 // 2*F + 1 ROUND CHANGE messages can start new round. 131 numStartNewRound = 2*f + 1 132 // F + 1 ROUND CHANGE messages can start catch up the round. 133 numCatchUp = f + 1 134 } 135 136 if num == numStartNewRound && (c.waitingForRoundChange || cv.Round.Cmp(roundView.Round) < 0) { 137 // We've received enough ROUND CHANGE messages, start a new round immediately. 138 logger.Warn("[RC] Prepare messages received before startNewRound", "round", cv.Round.String(), 139 "len(prepares)", c.current.Prepares.Size(), "messages", c.current.Prepares.GetMessages()) 140 logger.Warn("[RC] Commit messages received before startNewRound", "round", cv.Round.String(), 141 "len(commits)", c.current.Commits.Size(), "messages", c.current.Commits.GetMessages()) 142 logger.Warn("[RC] Received 2f+1 Round Change Messages. Starting new round", 143 "currentRound", cv.Round.String(), "newRound", roundView.Round.String()) 144 c.startNewRound(roundView.Round) 145 return nil 146 } else if c.waitingForRoundChange && num == numCatchUp { 147 // Once we received enough ROUND CHANGE messages, those messages form a weak certificate. 148 // If our round number is smaller than the certificate's round number, we would 149 // try to catch up the round number. 150 if cv.Round.Cmp(roundView.Round) < 0 { 151 logger.Warn("[RC] Send round change because we have f+1 round change messages", 152 "currentRound", cv.Round.String(), "newRound", roundView.Round.String()) 153 c.sendRoundChange(roundView.Round) 154 } 155 return nil 156 } else if cv.Round.Cmp(roundView.Round) < 0 { 157 // Only gossip the message with current round to other validators. 158 logger.Warn("[RC] Received round is bigger but not enough number of messages. Message ignored", 159 "currentRound", cv.Round.String(), "newRound", roundView.Round.String(), "numRC", num) 160 return errIgnored 161 } 162 return nil 163 } 164 165 // ---------------------------------------------------------------------------- 166 167 func newRoundChangeSet(valSet istanbul.ValidatorSet) *roundChangeSet { 168 return &roundChangeSet{ 169 validatorSet: valSet, 170 roundChanges: make(map[uint64]*messageSet), 171 mu: new(sync.Mutex), 172 } 173 } 174 175 type roundChangeSet struct { 176 validatorSet istanbul.ValidatorSet 177 roundChanges map[uint64]*messageSet 178 mu *sync.Mutex 179 } 180 181 // Add adds the round and message into round change set 182 func (rcs *roundChangeSet) Add(r *big.Int, msg *message) (int, error) { 183 rcs.mu.Lock() 184 defer rcs.mu.Unlock() 185 186 round := r.Uint64() 187 if rcs.roundChanges[round] == nil { 188 rcs.roundChanges[round] = newMessageSet(rcs.validatorSet) 189 } 190 err := rcs.roundChanges[round].Add(msg) 191 if err != nil { 192 return 0, err 193 } 194 return rcs.roundChanges[round].Size(), nil 195 } 196 197 // Clear deletes the messages with smaller round 198 func (rcs *roundChangeSet) Clear(round *big.Int) { 199 rcs.mu.Lock() 200 defer rcs.mu.Unlock() 201 202 for k, rms := range rcs.roundChanges { 203 if len(rms.Values()) == 0 || k < round.Uint64() { 204 delete(rcs.roundChanges, k) 205 } 206 } 207 } 208 209 // MaxRound returns the max round which the number of messages is equal or larger than num 210 func (rcs *roundChangeSet) MaxRound(num int) *big.Int { 211 rcs.mu.Lock() 212 defer rcs.mu.Unlock() 213 214 var maxRound *big.Int 215 for k, rms := range rcs.roundChanges { 216 if rms.Size() < num { 217 continue 218 } 219 r := big.NewInt(int64(k)) 220 if maxRound == nil || maxRound.Cmp(r) < 0 { 221 maxRound = r 222 } 223 } 224 return maxRound 225 }