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  }