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