github.com/annchain/OG@v0.0.9/consensus/bft/bft_partner.go (about) 1 // Copyright © 2019 Annchain Authors <EMAIL ADDRESS> 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package bft 16 17 import ( 18 "fmt" 19 "github.com/annchain/OG/arefactor/common/goroutine" 20 "github.com/annchain/OG/arefactor/og/types" 21 "strings" 22 "time" 23 24 "github.com/sirupsen/logrus" 25 ) 26 27 // DefaultBftPartner implements a Tendermint client according to "The latest gossip on BFT consensus" 28 // DefaultBftPartner is the action performer manipulating the BftStatus. 29 // It listens to the conditions changed outside (by message or by time) and perform actions. 30 // Note: Destroy and use a new one upon peers changing. 31 type DefaultBftPartner struct { 32 Id int 33 BftStatus *BftStatus 34 blockTime time.Duration 35 peerCommunicatorIncoming BftPeerCommunicatorIncoming 36 peerCommunicatorOutgoing BftPeerCommunicatorOutgoing 37 ProposalGenerator ProposalGenerator 38 ProposalValidator ProposalValidator 39 DecisionMaker DecisionMaker 40 41 WaiterTimeoutChannel chan *WaiterRequest 42 quit chan bool 43 waiter *Waiter 44 45 // event listener for a decision once made 46 ConsensusReachedListeners []ConsensusReachedListener 47 //wg sync.WaitGroup 48 } 49 50 func (p *DefaultBftPartner) GetBftPeerCommunicatorIncoming() BftPeerCommunicatorIncoming { 51 return p.peerCommunicatorIncoming 52 } 53 54 func NewDefaultBFTPartner(nbParticipants int, id int, blockTime time.Duration, 55 peerCommunicatorIncoming BftPeerCommunicatorIncoming, 56 peerCommunicatorOutgoing BftPeerCommunicatorOutgoing, 57 proposalGenerator ProposalGenerator, 58 proposalValidator ProposalValidator, 59 decisionMaker DecisionMaker, 60 peerInfo []BftPeer, 61 ) *DefaultBftPartner { 62 if nbParticipants < 2 { 63 panic(0) 64 } 65 p := &DefaultBftPartner{ 66 Id: id, 67 BftStatus: &BftStatus{ 68 N: nbParticipants, 69 F: (nbParticipants - 1) / 3, 70 Peers: peerInfo, 71 States: make(map[HeightRound]*HeightRoundState), 72 }, 73 blockTime: blockTime, 74 peerCommunicatorIncoming: peerCommunicatorIncoming, 75 peerCommunicatorOutgoing: peerCommunicatorOutgoing, 76 ProposalGenerator: proposalGenerator, 77 ProposalValidator: proposalValidator, 78 DecisionMaker: decisionMaker, 79 WaiterTimeoutChannel: make(chan *WaiterRequest, 10), 80 quit: make(chan bool), 81 } 82 83 // TODO: verify if the count is correct 84 // p.N == 3 *p.F+1 85 if p.BftStatus.N%3 == 1 { 86 p.BftStatus.Maj23 = 2*p.BftStatus.F + 1 87 } else { 88 p.BftStatus.Maj23 = MajorityTwoThird(p.BftStatus.N) 89 } 90 91 p.waiter = NewWaiter(p.WaiterTimeoutChannel) 92 93 logrus.WithField("n", p.BftStatus.N).WithField("F", p.BftStatus.F). 94 WithField("IM", p.Id). 95 WithField("maj23", p.BftStatus.Maj23).Debug("New BFT parnter generated") 96 return p 97 } 98 99 // RegisterConsensusReachedListener registers callback for decision made event 100 // TODO: In the future, protected the array so that it can handle term change 101 func (p *DefaultBftPartner) RegisterConsensusReachedListener(listener ConsensusReachedListener) { 102 p.ConsensusReachedListeners = append(p.ConsensusReachedListeners, listener) 103 } 104 105 func (p *DefaultBftPartner) Start() { 106 go p.WaiterLoop() 107 go p.EventLoop() 108 } 109 110 func (p *DefaultBftPartner) Stop() { 111 //quit channal is used by two or more go routines , use close instead of send values to channel 112 close(p.quit) 113 close(p.waiter.quit) 114 //p.wg.Wait() 115 logrus.Info("default partner stopped") 116 } 117 118 func MajorityTwoThird(n int) int { 119 return 2*n/3 + 1 120 } 121 122 func (p *DefaultBftPartner) Reset(nbParticipants int, id int) { 123 p.BftStatus.N = nbParticipants 124 p.BftStatus.F = (nbParticipants - 1) / 3 125 p.Id = id 126 if p.BftStatus.N%3 == 1 { 127 p.BftStatus.Maj23 = 2*p.BftStatus.F + 1 128 } else { 129 p.BftStatus.Maj23 = MajorityTwoThird(p.BftStatus.N) 130 } 131 logrus.WithField("maj23", p.BftStatus.Maj23).WithField("f", p.BftStatus.F). 132 WithField("nb", p.BftStatus.N).Info("reset bft") 133 return 134 } 135 136 //func (p *DefaultBftPartner) RestartNewEra() { 137 // s := p.BftStatus.States[p.BftStatus.CurrentHR] 138 // if s != nil { 139 // if s.Decision != nil { 140 // //p.BftStatus.States = make(map[p2p_message.HeightRound]*HeightRoundState) 141 // p.StartNewEra(p.BftStatus.CurrentHR.Height+1, 0) 142 // return 143 // } 144 // p.StartNewEra(p.BftStatus.CurrentHR.Height, p.BftStatus.CurrentHR.Round+1) 145 // return 146 // } 147 // //p.BftStatus.States = make(map[p2p_message.HeightRound]*HeightRoundState) 148 // p.StartNewEra(p.BftStatus.Curren R.Height, p.BftStatus.CurrentHR.Round) 149 // return 150 //} 151 152 func (p *DefaultBftPartner) WaiterLoop() { 153 goroutine.New(p.waiter.StartEventLoop) 154 } 155 156 // StartNewEra is called once height or round needs to be changed. 157 func (p *DefaultBftPartner) StartNewEra(height uint64, round int) { 158 hr := p.BftStatus.CurrentHR 159 if height-hr.Height > 1 { 160 logrus.WithField("height", height).Warn("height is much higher than current. Indicating packet loss or severe behind.") 161 } 162 hr.Height = height 163 hr.Round = round 164 165 logrus.WithFields(logrus.Fields{ 166 "IM": p.Id, 167 "currentHR": p.BftStatus.CurrentHR.String(), 168 "newHR": hr.String(), 169 }).Debug("Starting new round") 170 171 currState, _ := p.initHeightRound(hr) 172 // update partner height 173 p.BftStatus.CurrentHR = hr 174 175 p.WipeOldStates() 176 p.changeStep(StepTypePropose) 177 178 if p.Id == p.Proposer(p.BftStatus.CurrentHR) { 179 logrus.WithField("IM", p.Id).WithField("hr", p.BftStatus.CurrentHR.String()).Debug("I'm the proposer") 180 var proposal Proposal 181 var validCondition ProposalCondition 182 if currState.ValidValue != nil { 183 logrus.WithField("hr ", hr).Trace("proposal will use current state valid value") 184 proposal = currState.ValidValue 185 } else { 186 if round == 0 { 187 logrus.WithField("hr ", hr).Trace("proposal will use value in new height") 188 proposal, validCondition = p.GetValue(true) 189 } else { 190 logrus.WithField("hr ", hr).Trace("proposal will use value in new round") 191 proposal, validCondition = p.GetValue(false) 192 } 193 if validCondition.ValidHeight != p.BftStatus.CurrentHR.Height { 194 // if p.BftStatus.CurrentHR.Height > validCondition.ValidHeight { 195 // //TODO: I received a history message. should be ok? 196 // logrus.WithField("height", p.BftStatus.CurrentHR).WithField("valid height ", validCondition).Warn("height mismatch //TODO") 197 // } else { 198 // // 199 // logrus.WithField("height", p.BftStatus.CurrentHR).WithField("valid height ", validCondition).Debug("height mismatch //TODO") 200 // } 201 // 202 } 203 } 204 logrus.WithField("proposal ", proposal).Trace("new proposal generated") 205 // broadcastWaiterTimeoutChannel 206 p.Broadcast(BftMessageTypeProposal, p.BftStatus.CurrentHR, proposal, currState.ValidRound) 207 } else { 208 p.WaitStepTimeout(StepTypePropose, TimeoutPropose, p.BftStatus.CurrentHR, p.OnTimeoutPropose) 209 } 210 } 211 212 func (p *DefaultBftPartner) EventLoop() { 213 //goroutine.New(p.send) 214 //p.wg.Add(1) 215 goroutine.New(p.receive) 216 //p.wg.Add(1) 217 } 218 219 // receive prevents concurrent state updates by allowing only one channel to be read per loop 220 // Any action which involves state updates should be in this select clause 221 func (p *DefaultBftPartner) receive() { 222 //defer p.wg.Done() 223 timer := time.NewTimer(time.Second * 7) 224 pipeOutChannel := p.peerCommunicatorIncoming.GetPipeOut() 225 for { 226 timer.Reset(time.Second * 7) 227 select { 228 case <-p.quit: 229 logrus.Info("got quit msg, bft partner receive routine will stop") 230 return 231 case v := <-p.WaiterTimeoutChannel: 232 context := v.Context.(*TendermintContext) 233 logrus.WithFields(logrus.Fields{ 234 "step": context.StepType.String(), 235 "IM": p.Id, 236 "hr": context.HeightRound.String(), 237 }).Warn("wait step timeout") 238 p.dumpAll("wait step timeout") 239 v.TimeoutCallback(v.Context) 240 case <-timer.C: 241 logrus.WithField("IM", p.Id).Debug("Blocked reading incoming bft") 242 p.dumpAll("blocked reading incoming") 243 case msg := <-pipeOutChannel: 244 p.handleMessage(msg) 245 } 246 } 247 } 248 249 // Proposer returns current round proposer. Now simply round robin 250 func (p *DefaultBftPartner) Proposer(hr HeightRound) int { 251 //return 3 252 //return (hr.Height + hr.Round) % p.N 253 //maybe overflow 254 return (int(hr.Height%uint64(p.BftStatus.N)) + hr.Round%p.BftStatus.N) % p.BftStatus.N 255 } 256 257 // GetValue generates the value requiring consensus 258 func (p *DefaultBftPartner) GetValue(newBlock bool) (Proposal, ProposalCondition) { 259 //don't sleep for the same height new round 260 blockTime := time.After(p.blockTime) 261 if newBlock { 262 logrus.WithField("blocktime", p.blockTime).Debug("will return a proposal after some time") 263 select { 264 case <-p.quit: 265 logrus.Info("got stop signal") 266 case <-blockTime: 267 break 268 } 269 //time.Sleep(p.blockTime) 270 } 271 272 if p.ProposalGenerator != nil { 273 pro, validHeight := p.ProposalGenerator.ProduceProposal() 274 logrus.WithField("proposal", pro).Debug("proposal gen") 275 return pro, validHeight 276 } 277 v := fmt.Sprintf("■■■%d %d■■■", p.BftStatus.CurrentHR.Height, p.BftStatus.CurrentHR.Round) 278 s := StringProposal{ 279 Content: v, 280 } 281 logrus.WithField("proposal", s).Info("proposal generated") 282 return &s, ProposalCondition{p.BftStatus.CurrentHR.Height} 283 } 284 285 // Multicast encapsulate messages to all partners 286 // 287 func (p *DefaultBftPartner) Broadcast(messageType BftMessageType, hr HeightRound, content Proposal, validRound int) { 288 var m BftMessage 289 290 basicInfo := BftBasicInfo{ 291 HeightRound: hr, 292 SourceId: uint16(p.Id), 293 } 294 var idv *types.Hash 295 if content != nil { 296 cIdv := content.GetId() 297 if cIdv != nil { 298 idv = cIdv 299 } 300 301 } 302 switch messageType { 303 case BftMessageTypeProposal: 304 m = &BftMessageProposal{ 305 BftBasicInfo: basicInfo, 306 Value: content, 307 ValidRound: validRound, 308 } 309 case BftMessageTypePreVote: 310 m = &BftMessagePreVote{ 311 BftBasicInfo: basicInfo, 312 Idv: idv, 313 } 314 case BftMessageTypePreCommit: 315 m = &BftMessagePreCommit{ 316 BftBasicInfo: basicInfo, 317 Idv: idv, 318 } 319 } 320 p.peerCommunicatorOutgoing.Broadcast(m, p.BftStatus.Peers) 321 } 322 323 // OnTimeoutPropose is the callback after staying too long on propose step 324 func (p *DefaultBftPartner) OnTimeoutPropose(context WaiterContext) { 325 v := context.(*TendermintContext) 326 if v.HeightRound == p.BftStatus.CurrentHR && p.BftStatus.States[p.BftStatus.CurrentHR].Step == StepTypePropose { 327 p.Broadcast(BftMessageTypePreVote, p.BftStatus.CurrentHR, nil, 0) 328 p.changeStep(StepTypePreVote) 329 } 330 } 331 332 // OnTimeoutPreVote is the callback after staying too long on prevote step 333 func (p *DefaultBftPartner) OnTimeoutPreVote(context WaiterContext) { 334 v := context.(*TendermintContext) 335 if v.HeightRound == p.BftStatus.CurrentHR && p.BftStatus.States[p.BftStatus.CurrentHR].Step == StepTypePreVote { 336 p.Broadcast(BftMessageTypePreCommit, p.BftStatus.CurrentHR, nil, 0) 337 p.changeStep(StepTypePreCommit) 338 } 339 } 340 341 // OnTimeoutPreCommit is the callback after staying too long on precommit step 342 func (p *DefaultBftPartner) OnTimeoutPreCommit(context WaiterContext) { 343 v := context.(*TendermintContext) 344 if v.HeightRound == p.BftStatus.CurrentHR { 345 p.StartNewEra(v.HeightRound.Height, v.HeightRound.Round+1) 346 } 347 } 348 349 // WaitStepTimeout waits for a centain time if stepType stays too long 350 func (p *DefaultBftPartner) WaitStepTimeout(stepType StepType, timeout time.Duration, hr HeightRound, timeoutCallback func(WaiterContext)) { 351 p.waiter.UpdateRequest(&WaiterRequest{ 352 WaitTime: timeout, 353 TimeoutCallback: timeoutCallback, 354 Context: &TendermintContext{ 355 HeightRound: hr, 356 StepType: stepType, 357 }, 358 }) 359 } 360 361 func (p *DefaultBftPartner) handleMessage(messageEvent *BftMessageEvent) { 362 message := messageEvent.Message 363 switch message.GetType() { 364 case BftMessageTypeProposal: 365 msg, ok := message.(*BftMessageProposal) 366 if !ok { 367 logrus.Warn("it claims to be a BftMessageProposal but the payload does not align") 368 return 369 } 370 371 if needHandle := p.checkRound(&msg.BftBasicInfo); !needHandle { 372 // out-of-date messages, ignore 373 break 374 } 375 logrus.WithFields(logrus.Fields{ 376 "IM": p.Id, 377 "hr": p.BftStatus.CurrentHR.String(), 378 "type": message.GetType().String(), 379 "from": msg.SourceId, 380 "fromHr": msg.HeightRound.String(), 381 "value": msg.Value, 382 }).Debug("In") 383 p.handleProposal(msg) 384 case BftMessageTypePreVote: 385 msg, ok := message.(*BftMessagePreVote) 386 if !ok { 387 logrus.Warn("it claims to be a BftMessagePreVote but the payload does not align") 388 return 389 } 390 if needHandle := p.checkRound(&msg.BftBasicInfo); !needHandle { 391 // out-of-date messages, ignore 392 break 393 } 394 p.BftStatus.States[msg.HeightRound].PreVotes[msg.SourceId] = msg 395 logrus.WithFields(logrus.Fields{ 396 "IM": p.Id, 397 "hr": p.BftStatus.CurrentHR.String(), 398 "type": message.GetType().String(), 399 "from": msg.SourceId, 400 "fromHr": msg.HeightRound.String(), 401 }).Debug("In") 402 p.handlePreVote(msg) 403 case BftMessageTypePreCommit: 404 msg, ok := message.(*BftMessagePreCommit) 405 if !ok { 406 logrus.Warn("it claims to be a BftMessagePreCommit but the payload does not align") 407 return 408 } 409 if needHandle := p.checkRound(&msg.BftBasicInfo); !needHandle { 410 // out-of-date messages, ignore 411 break 412 } 413 perC := *msg 414 p.BftStatus.States[msg.HeightRound].PreCommits[msg.SourceId] = &perC 415 logrus.WithFields(logrus.Fields{ 416 "IM": p.Id, 417 "hr": p.BftStatus.CurrentHR.String(), 418 "type": message.GetType().String(), 419 "from": msg.SourceId, 420 "fromHr": msg.HeightRound.String(), 421 }).Debug("In") 422 p.handlePreCommit(msg) 423 default: 424 logrus.WithField("type", message.GetType()).Warn("unknown bft message type") 425 } 426 427 } 428 func (p *DefaultBftPartner) handleProposal(proposal *BftMessageProposal) { 429 state, ok := p.BftStatus.States[proposal.HeightRound] 430 if !ok { 431 logrus.WithField("IM", p.Id).WithField("hr", proposal.HeightRound).Error("proposal height round not in states") 432 panic("must exists") 433 } 434 state.MessageProposal = proposal 435 ////if this is proposed by me , send precommit 436 //if proposal.SourceId == uint16(p.MyIndex) { 437 // p.Multicast(BftMessageTypePreVote, proposal.HeightRound, proposal.Value, 0) 438 // p.changeStep(StepTypePreVote) 439 // return 440 //} 441 // rule line 22 442 if state.Step == StepTypePropose { 443 if p.valid(proposal.Value) && (state.LockedRound == -1 || state.LockedValue.Equal(proposal.Value)) { 444 p.Broadcast(BftMessageTypePreVote, proposal.HeightRound, proposal.Value, 0) 445 } else { 446 p.Broadcast(BftMessageTypePreVote, proposal.HeightRound, nil, 0) 447 } 448 p.changeStep(StepTypePreVote) 449 } 450 451 // rule line 28 452 count := p.count(BftMessageTypePreVote, proposal.HeightRound.Height, proposal.ValidRound, MatchTypeByValue, proposal.Value.GetId()) 453 if count >= p.BftStatus.Maj23 { 454 if state.Step == StepTypePropose && (proposal.ValidRound >= 0 && proposal.ValidRound < p.BftStatus.CurrentHR.Round) { 455 if p.valid(proposal.Value) && (state.LockedRound <= proposal.ValidRound || state.LockedValue.Equal(proposal.Value)) { 456 p.Broadcast(BftMessageTypePreVote, proposal.HeightRound, proposal.Value, 0) 457 } else { 458 p.Broadcast(BftMessageTypePreVote, proposal.HeightRound, nil, 0) 459 } 460 p.changeStep(StepTypePreVote) 461 } 462 } 463 } 464 func (p *DefaultBftPartner) handlePreVote(vote *BftMessagePreVote) { 465 // rule line 34 466 count := p.count(BftMessageTypePreVote, vote.HeightRound.Height, vote.HeightRound.Round, MatchTypeAny, nil) 467 state, ok := p.BftStatus.States[vote.HeightRound] 468 if !ok { 469 panic("should exists: " + vote.HeightRound.String()) 470 } 471 if count >= p.BftStatus.Maj23 { 472 if state.Step == StepTypePreVote && !state.StepTypeEqualPreVoteTriggered { 473 logrus.WithField("IM", p.Id).WithField("hr", vote.HeightRound.String()).Debug("prevote counter is more than 2f+1 #1") 474 state.StepTypeEqualPreVoteTriggered = true 475 p.WaitStepTimeout(StepTypePreVote, TimeoutPreVote, vote.HeightRound, p.OnTimeoutPreVote) 476 } 477 } 478 // rule line 36 479 if state.MessageProposal != nil && count >= p.BftStatus.Maj23 { 480 if p.valid(state.MessageProposal.Value) && state.Step >= StepTypePreVote && !state.StepTypeEqualOrLargerPreVoteTriggered { 481 logrus.WithField("IM", p.Id).WithField("hr", vote.HeightRound.String()).Debug("prevote counter is more than 2f+1 #2") 482 state.StepTypeEqualOrLargerPreVoteTriggered = true 483 if state.Step == StepTypePreVote { 484 state.LockedValue = state.MessageProposal.Value 485 state.LockedRound = p.BftStatus.CurrentHR.Round 486 p.Broadcast(BftMessageTypePreCommit, vote.HeightRound, state.MessageProposal.Value, 0) 487 p.changeStep(StepTypePreCommit) 488 } 489 state.ValidValue = state.MessageProposal.Value 490 state.ValidRound = p.BftStatus.CurrentHR.Round 491 } 492 } 493 // rule line 44 494 count = p.count(BftMessageTypePreVote, vote.HeightRound.Height, vote.HeightRound.Round, MatchTypeNil, nil) 495 if count >= p.BftStatus.Maj23 && state.Step == StepTypePreVote { 496 logrus.WithField("IM", p.Id).WithField("hr", p.BftStatus.CurrentHR.String()).Debug("prevote counter is more than 2f+1 #3") 497 p.Broadcast(BftMessageTypePreCommit, vote.HeightRound, nil, 0) 498 p.changeStep(StepTypePreCommit) 499 } 500 501 } 502 503 func (p *DefaultBftPartner) handlePreCommit(commit *BftMessagePreCommit) { 504 // rule line 47 505 count := p.count(BftMessageTypePreCommit, commit.HeightRound.Height, commit.HeightRound.Round, MatchTypeAny, nil) 506 state := p.BftStatus.States[commit.HeightRound] 507 if count >= p.BftStatus.Maj23 && !state.StepTypeEqualPreCommitTriggered { 508 state.StepTypeEqualPreCommitTriggered = true 509 p.WaitStepTimeout(StepTypePreCommit, TimeoutPreCommit, commit.HeightRound, p.OnTimeoutPreCommit) 510 } 511 // rule line 49 512 if state.MessageProposal != nil { 513 count = p.count(BftMessageTypePreCommit, commit.HeightRound.Height, commit.HeightRound.Round, MatchTypeByValue, state.MessageProposal.Value.GetId()) 514 if count >= p.BftStatus.Maj23 { 515 if state.Decision == nil { 516 // try to validate if we really got a decision 517 // This step is usually for value validation 518 decision, err := p.DecisionMaker.MakeDecision(state.MessageProposal.Value, state) 519 if err != nil { 520 logrus.WithError(err).WithField("hr", p.BftStatus.CurrentHR).Warn("validation failed for decision") 521 if count == p.BftStatus.N { 522 logrus.WithField("hr", p.BftStatus.CurrentHR).Warn("all messages received but not a good decision. Abandom this round") 523 p.StartNewEra(p.BftStatus.CurrentHR.Height, p.BftStatus.CurrentHR.Round+1) 524 } else { 525 logrus.Warn("wait for more correct messages coming") 526 } 527 return 528 } 529 530 // output decision 531 state.Decision = decision 532 logrus.WithFields(logrus.Fields{ 533 "IM": p.Id, 534 "hr": p.BftStatus.CurrentHR.String(), 535 "value": state.Decision, 536 }).Info("Decision made") 537 538 //send the decision to upper client to process 539 p.notifyDecisionMade(p.BftStatus.CurrentHR, state.Decision) 540 // TODO: StartNewEra should be called outside the bft in order to reflect term change. 541 // You cannot start new era with height++ by yourself since you are not sure whether you are in the next group 542 // Annsensus knows that. 543 p.StartNewEra(p.BftStatus.CurrentHR.Height+1, 0) 544 } 545 } 546 } 547 } 548 549 // valid checks proposal validation 550 func (p *DefaultBftPartner) valid(proposal Proposal) bool { 551 err := p.ProposalValidator.ValidateProposal(proposal) 552 return err == nil 553 } 554 555 // count votes and commits from others. 556 func (p *DefaultBftPartner) count(messageType BftMessageType, height uint64, validRound int, valueIdMatchType ValueIdMatchType, valueId *types.Hash) int { 557 counter := 0 558 559 state, ok := p.BftStatus.States[HeightRound{ 560 Height: height, 561 Round: validRound, 562 }] 563 if !ok { 564 return 0 565 } 566 switch messageType { 567 case BftMessageTypePreVote: 568 target := state.PreVotes 569 for _, m := range target { 570 if m == nil { 571 continue 572 } 573 if m.HeightRound.Height > height || m.HeightRound.Round > validRound { 574 p.dumpAll("impossible now") 575 panic("wrong logic: " + fmt.Sprintf("%d %d %d %d", m.HeightRound.Height, height, m.HeightRound.Round, validRound)) 576 } 577 switch valueIdMatchType { 578 case MatchTypeByValue: 579 if m.Idv == valueId { 580 counter++ 581 } 582 case MatchTypeNil: 583 if m.Idv == nil { 584 counter++ 585 } 586 case MatchTypeAny: 587 counter++ 588 } 589 } 590 case BftMessageTypePreCommit: 591 target := state.PreCommits 592 for _, m := range target { 593 if m == nil { 594 continue 595 } 596 if m.HeightRound.Height > height || m.HeightRound.Round > validRound { 597 p.dumpAll("impossible now") 598 panic("wrong logic: " + fmt.Sprintf("%d %d %d %d", m.HeightRound.Height, height, m.HeightRound.Round, validRound)) 599 } 600 switch valueIdMatchType { 601 case MatchTypeByValue: 602 if m.Idv == nil { 603 if valueId == nil { 604 counter++ 605 } 606 } else if valueId != nil && *valueId == *m.Idv { 607 counter++ 608 } 609 case MatchTypeNil: 610 if m.Idv == nil { 611 counter++ 612 } 613 case MatchTypeAny: 614 counter++ 615 } 616 } 617 default: 618 //panic("not implemented") 619 } 620 logrus.WithField("IM", p.Id). 621 Debugf("Counting: [%d] %s H:%d VR:%d MT:%d", counter, messageType.String(), height, validRound, valueIdMatchType) 622 return counter 623 } 624 625 // checkRound will init all data structure this message needs. 626 // It also check if the message is out of date, or advanced too much 627 func (p *DefaultBftPartner) checkRound(message *BftBasicInfo) (needHandle bool) { 628 // check status storage first 629 if message.HeightRound.IsAfterOrEqual(p.BftStatus.CurrentHR) { 630 _, ok := p.BftStatus.States[message.HeightRound] 631 if !ok { 632 // create one 633 // TODO: verify if someone is generating garbage height 634 p.initHeightRound(message.HeightRound) 635 } 636 } else { 637 // this is an old message. just discard it since we don't need to process old messages. 638 logrus.WithField("IM", p.Id).WithField("hr", message.HeightRound).Debug("received an old message") 639 return false 640 } 641 // rule line 55 642 // slightly changed this so that if there is f+1 newer HeightRound(instead of just round), catch up to this HeightRound 643 if message.HeightRound.IsAfter(p.BftStatus.CurrentHR) { 644 state, _ := p.BftStatus.States[message.HeightRound] 645 //if !ok { 646 // // create one 647 // 648 // d, c := p.initHeightRound(message.HeightRound) 649 // state = d 650 // if c != len(p.BftStatus.States) { 651 // panic("number not aligned") 652 // } 653 //} 654 state.Sources[message.SourceId] = true 655 logrus.WithField("IM", p.Id).Tracef("Set source: %d at %s, %+v", message.SourceId, message.HeightRound.String(), state.Sources) 656 if _, ok := p.BftStatus.States[message.HeightRound]; !ok { 657 panic(fmt.Sprintf("fuck %d %s", p.Id, p.BftStatus.CurrentHR.String())) 658 } 659 //logrus.WithField("IM", p.MyIndex).Tracef("%d's %s state is %+v, after receiving message %s from %d", 660 // p.MyIndex, p.BftStatus.CurrentHR.String(), 661 // p.BftStatus.States[p.BftStatus.CurrentHR].Sources, message.HeightRound.String(), message.SourceId) 662 663 if len(state.Sources) >= p.BftStatus.F+1 { 664 p.dumpAll("New era received") 665 p.StartNewEra(message.HeightRound.Height, message.HeightRound.Round) 666 } 667 } 668 669 return message.HeightRound.IsAfterOrEqual(p.BftStatus.CurrentHR) 670 } 671 672 // changeStep updates the step and then notify the waiter. 673 func (p *DefaultBftPartner) changeStep(stepType StepType) { 674 p.BftStatus.States[p.BftStatus.CurrentHR].Step = stepType 675 p.waiter.UpdateContext(&TendermintContext{ 676 HeightRound: p.BftStatus.CurrentHR, 677 StepType: stepType, 678 }) 679 } 680 681 // dumpVotes prints all current votes received 682 func (p *DefaultBftPartner) dumpVotes(votes []*BftMessagePreVote) string { 683 sb := strings.Builder{} 684 sb.WriteString("[") 685 for _, vote := range votes { 686 if vote == nil { 687 sb.WriteString(fmt.Sprintf("[nil Vote]")) 688 } else { 689 sb.WriteString(fmt.Sprintf("[%d hr:%s s:%s]", vote.SourceId, vote.HeightRound.String(), vote.Idv)) 690 } 691 692 sb.WriteString(" ") 693 } 694 sb.WriteString("]") 695 return sb.String() 696 } 697 698 // dumpVotes prints all current votes received 699 func (p *DefaultBftPartner) dumpCommits(votes []*BftMessagePreCommit) string { 700 sb := strings.Builder{} 701 sb.WriteString("[") 702 for _, vote := range votes { 703 if vote == nil { 704 sb.WriteString(fmt.Sprintf("[nil Vote]")) 705 } else { 706 sb.WriteString(fmt.Sprintf("[%d hr:%s s:%s]", vote.SourceId, vote.HeightRound.String(), vote.Idv)) 707 } 708 709 sb.WriteString(" ") 710 } 711 sb.WriteString("]") 712 return sb.String() 713 } 714 715 func (p *DefaultBftPartner) dumpAll(reason string) { 716 //return 717 state := p.BftStatus.States[p.BftStatus.CurrentHR] 718 if state == nil { 719 logrus.WithField("IM", p.Id).WithField("hr", p.BftStatus.CurrentHR).WithField("reason", reason).Debug("Dumping nil state") 720 return 721 } 722 logrus.WithField("IM", p.Id).WithField("hr", p.BftStatus.CurrentHR).WithField("reason", reason).Debug("Dumping") 723 logrus.WithField("IM", p.Id).WithField("hr", p.BftStatus.CurrentHR).WithField("votes", "prevotes").Debug(p.dumpVotes(state.PreVotes)) 724 logrus.WithField("IM", p.Id).WithField("hr", p.BftStatus.CurrentHR).WithField("votes", "precommits").Debug(p.dumpCommits(state.PreCommits)) 725 logrus.WithField("IM", p.Id).WithField("hr", p.BftStatus.CurrentHR).WithField("step", state.Step.String()).Debug("Step") 726 logrus.WithField("IM", p.Id).WithField("hr", p.BftStatus.CurrentHR).Debugf("%+v %d", state.Sources, len(state.Sources)) 727 } 728 729 func (p *DefaultBftPartner) WipeOldStates() { 730 var toRemove []HeightRound 731 for hr := range p.BftStatus.States { 732 if hr.IsBefore(p.BftStatus.CurrentHR) { 733 toRemove = append(toRemove, hr) 734 } 735 } 736 for _, hr := range toRemove { 737 delete(p.BftStatus.States, hr) 738 } 739 } 740 741 func (p *DefaultBftPartner) initHeightRound(hr HeightRound) (*HeightRoundState, int) { 742 // first check if there is previous message received 743 if _, ok := p.BftStatus.States[hr]; !ok { 744 // init one 745 p.BftStatus.States[hr] = NewHeightRoundState(p.BftStatus.N) 746 logrus.WithField("hr", hr).WithField("IM", p.Id).Debug("inited heightround") 747 } 748 return p.BftStatus.States[hr], len(p.BftStatus.States) 749 } 750 751 type BftStatusReport struct { 752 HeightRound HeightRound 753 States HeightRoundStateMap 754 Now time.Time 755 } 756 757 func (p *DefaultBftPartner) Status() interface{} { 758 status := BftStatusReport{} 759 status.HeightRound = p.BftStatus.CurrentHR 760 status.States = p.BftStatus.States 761 status.Now = time.Now() 762 return &status 763 } 764 765 func (p *DefaultBftPartner) notifyDecisionMade(round HeightRound, decision ConsensusDecision) { 766 for _, listener := range p.ConsensusReachedListeners { 767 listener.GetConsensusDecisionMadeEventChannel() <- decision 768 } 769 }