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