github.com/evdatsion/aphelion-dpos-bft@v0.32.1/consensus/types/height_vote_set.go (about)

     1  package types
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  	"sync"
     8  
     9  	"github.com/evdatsion/aphelion-dpos-bft/p2p"
    10  	"github.com/evdatsion/aphelion-dpos-bft/types"
    11  )
    12  
    13  type RoundVoteSet struct {
    14  	Prevotes   *types.VoteSet
    15  	Precommits *types.VoteSet
    16  }
    17  
    18  var (
    19  	GotVoteFromUnwantedRoundError = errors.New("Peer has sent a vote that does not match our round for more than one round")
    20  )
    21  
    22  /*
    23  Keeps track of all VoteSets from round 0 to round 'round'.
    24  
    25  Also keeps track of up to one RoundVoteSet greater than
    26  'round' from each peer, to facilitate catchup syncing of commits.
    27  
    28  A commit is +2/3 precommits for a block at a round,
    29  but which round is not known in advance, so when a peer
    30  provides a precommit for a round greater than mtx.round,
    31  we create a new entry in roundVoteSets but also remember the
    32  peer to prevent abuse.
    33  We let each peer provide us with up to 2 unexpected "catchup" rounds.
    34  One for their LastCommit round, and another for the official commit round.
    35  */
    36  type HeightVoteSet struct {
    37  	chainID string
    38  	height  int64
    39  	valSet  *types.ValidatorSet
    40  
    41  	mtx               sync.Mutex
    42  	round             int                  // max tracked round
    43  	roundVoteSets     map[int]RoundVoteSet // keys: [0...round]
    44  	peerCatchupRounds map[p2p.ID][]int     // keys: peer.ID; values: at most 2 rounds
    45  }
    46  
    47  func NewHeightVoteSet(chainID string, height int64, valSet *types.ValidatorSet) *HeightVoteSet {
    48  	hvs := &HeightVoteSet{
    49  		chainID: chainID,
    50  	}
    51  	hvs.Reset(height, valSet)
    52  	return hvs
    53  }
    54  
    55  func (hvs *HeightVoteSet) Reset(height int64, valSet *types.ValidatorSet) {
    56  	hvs.mtx.Lock()
    57  	defer hvs.mtx.Unlock()
    58  
    59  	hvs.height = height
    60  	hvs.valSet = valSet
    61  	hvs.roundVoteSets = make(map[int]RoundVoteSet)
    62  	hvs.peerCatchupRounds = make(map[p2p.ID][]int)
    63  
    64  	hvs.addRound(0)
    65  	hvs.round = 0
    66  }
    67  
    68  func (hvs *HeightVoteSet) Height() int64 {
    69  	hvs.mtx.Lock()
    70  	defer hvs.mtx.Unlock()
    71  	return hvs.height
    72  }
    73  
    74  func (hvs *HeightVoteSet) Round() int {
    75  	hvs.mtx.Lock()
    76  	defer hvs.mtx.Unlock()
    77  	return hvs.round
    78  }
    79  
    80  // Create more RoundVoteSets up to round.
    81  func (hvs *HeightVoteSet) SetRound(round int) {
    82  	hvs.mtx.Lock()
    83  	defer hvs.mtx.Unlock()
    84  	if hvs.round != 0 && (round < hvs.round+1) {
    85  		panic("SetRound() must increment hvs.round")
    86  	}
    87  	for r := hvs.round + 1; r <= round; r++ {
    88  		if _, ok := hvs.roundVoteSets[r]; ok {
    89  			continue // Already exists because peerCatchupRounds.
    90  		}
    91  		hvs.addRound(r)
    92  	}
    93  	hvs.round = round
    94  }
    95  
    96  func (hvs *HeightVoteSet) addRound(round int) {
    97  	if _, ok := hvs.roundVoteSets[round]; ok {
    98  		panic("addRound() for an existing round")
    99  	}
   100  	// log.Debug("addRound(round)", "round", round)
   101  	prevotes := types.NewVoteSet(hvs.chainID, hvs.height, round, types.PrevoteType, hvs.valSet)
   102  	precommits := types.NewVoteSet(hvs.chainID, hvs.height, round, types.PrecommitType, hvs.valSet)
   103  	hvs.roundVoteSets[round] = RoundVoteSet{
   104  		Prevotes:   prevotes,
   105  		Precommits: precommits,
   106  	}
   107  }
   108  
   109  // Duplicate votes return added=false, err=nil.
   110  // By convention, peerID is "" if origin is self.
   111  func (hvs *HeightVoteSet) AddVote(vote *types.Vote, peerID p2p.ID) (added bool, err error) {
   112  	hvs.mtx.Lock()
   113  	defer hvs.mtx.Unlock()
   114  	if !types.IsVoteTypeValid(vote.Type) {
   115  		return
   116  	}
   117  	voteSet := hvs.getVoteSet(vote.Round, vote.Type)
   118  	if voteSet == nil {
   119  		if rndz := hvs.peerCatchupRounds[peerID]; len(rndz) < 2 {
   120  			hvs.addRound(vote.Round)
   121  			voteSet = hvs.getVoteSet(vote.Round, vote.Type)
   122  			hvs.peerCatchupRounds[peerID] = append(rndz, vote.Round)
   123  		} else {
   124  			// punish peer
   125  			err = GotVoteFromUnwantedRoundError
   126  			return
   127  		}
   128  	}
   129  	added, err = voteSet.AddVote(vote)
   130  	return
   131  }
   132  
   133  func (hvs *HeightVoteSet) Prevotes(round int) *types.VoteSet {
   134  	hvs.mtx.Lock()
   135  	defer hvs.mtx.Unlock()
   136  	return hvs.getVoteSet(round, types.PrevoteType)
   137  }
   138  
   139  func (hvs *HeightVoteSet) Precommits(round int) *types.VoteSet {
   140  	hvs.mtx.Lock()
   141  	defer hvs.mtx.Unlock()
   142  	return hvs.getVoteSet(round, types.PrecommitType)
   143  }
   144  
   145  // Last round and blockID that has +2/3 prevotes for a particular block or nil.
   146  // Returns -1 if no such round exists.
   147  func (hvs *HeightVoteSet) POLInfo() (polRound int, polBlockID types.BlockID) {
   148  	hvs.mtx.Lock()
   149  	defer hvs.mtx.Unlock()
   150  	for r := hvs.round; r >= 0; r-- {
   151  		rvs := hvs.getVoteSet(r, types.PrevoteType)
   152  		polBlockID, ok := rvs.TwoThirdsMajority()
   153  		if ok {
   154  			return r, polBlockID
   155  		}
   156  	}
   157  	return -1, types.BlockID{}
   158  }
   159  
   160  func (hvs *HeightVoteSet) getVoteSet(round int, type_ types.SignedMsgType) *types.VoteSet {
   161  	rvs, ok := hvs.roundVoteSets[round]
   162  	if !ok {
   163  		return nil
   164  	}
   165  	switch type_ {
   166  	case types.PrevoteType:
   167  		return rvs.Prevotes
   168  	case types.PrecommitType:
   169  		return rvs.Precommits
   170  	default:
   171  		panic(fmt.Sprintf("Unexpected vote type %X", type_))
   172  	}
   173  }
   174  
   175  // If a peer claims that it has 2/3 majority for given blockKey, call this.
   176  // NOTE: if there are too many peers, or too much peer churn,
   177  // this can cause memory issues.
   178  // TODO: implement ability to remove peers too
   179  func (hvs *HeightVoteSet) SetPeerMaj23(round int, type_ types.SignedMsgType, peerID p2p.ID, blockID types.BlockID) error {
   180  	hvs.mtx.Lock()
   181  	defer hvs.mtx.Unlock()
   182  	if !types.IsVoteTypeValid(type_) {
   183  		return fmt.Errorf("SetPeerMaj23: Invalid vote type %v", type_)
   184  	}
   185  	voteSet := hvs.getVoteSet(round, type_)
   186  	if voteSet == nil {
   187  		return nil // something we don't know about yet
   188  	}
   189  	return voteSet.SetPeerMaj23(types.P2PID(peerID), blockID)
   190  }
   191  
   192  //---------------------------------------------------------
   193  // string and json
   194  
   195  func (hvs *HeightVoteSet) String() string {
   196  	return hvs.StringIndented("")
   197  }
   198  
   199  func (hvs *HeightVoteSet) StringIndented(indent string) string {
   200  	hvs.mtx.Lock()
   201  	defer hvs.mtx.Unlock()
   202  	vsStrings := make([]string, 0, (len(hvs.roundVoteSets)+1)*2)
   203  	// rounds 0 ~ hvs.round inclusive
   204  	for round := 0; round <= hvs.round; round++ {
   205  		voteSetString := hvs.roundVoteSets[round].Prevotes.StringShort()
   206  		vsStrings = append(vsStrings, voteSetString)
   207  		voteSetString = hvs.roundVoteSets[round].Precommits.StringShort()
   208  		vsStrings = append(vsStrings, voteSetString)
   209  	}
   210  	// all other peer catchup rounds
   211  	for round, roundVoteSet := range hvs.roundVoteSets {
   212  		if round <= hvs.round {
   213  			continue
   214  		}
   215  		voteSetString := roundVoteSet.Prevotes.StringShort()
   216  		vsStrings = append(vsStrings, voteSetString)
   217  		voteSetString = roundVoteSet.Precommits.StringShort()
   218  		vsStrings = append(vsStrings, voteSetString)
   219  	}
   220  	return fmt.Sprintf(`HeightVoteSet{H:%v R:0~%v
   221  %s  %v
   222  %s}`,
   223  		hvs.height, hvs.round,
   224  		indent, strings.Join(vsStrings, "\n"+indent+"  "),
   225  		indent)
   226  }
   227  
   228  func (hvs *HeightVoteSet) MarshalJSON() ([]byte, error) {
   229  	hvs.mtx.Lock()
   230  	defer hvs.mtx.Unlock()
   231  
   232  	allVotes := hvs.toAllRoundVotes()
   233  	return cdc.MarshalJSON(allVotes)
   234  }
   235  
   236  func (hvs *HeightVoteSet) toAllRoundVotes() []roundVotes {
   237  	totalRounds := hvs.round + 1
   238  	allVotes := make([]roundVotes, totalRounds)
   239  	// rounds 0 ~ hvs.round inclusive
   240  	for round := 0; round < totalRounds; round++ {
   241  		allVotes[round] = roundVotes{
   242  			Round:              round,
   243  			Prevotes:           hvs.roundVoteSets[round].Prevotes.VoteStrings(),
   244  			PrevotesBitArray:   hvs.roundVoteSets[round].Prevotes.BitArrayString(),
   245  			Precommits:         hvs.roundVoteSets[round].Precommits.VoteStrings(),
   246  			PrecommitsBitArray: hvs.roundVoteSets[round].Precommits.BitArrayString(),
   247  		}
   248  	}
   249  	// TODO: all other peer catchup rounds
   250  	return allVotes
   251  }
   252  
   253  type roundVotes struct {
   254  	Round              int      `json:"round"`
   255  	Prevotes           []string `json:"prevotes"`
   256  	PrevotesBitArray   string   `json:"prevotes_bit_array"`
   257  	Precommits         []string `json:"precommits"`
   258  	PrecommitsBitArray string   `json:"precommits_bit_array"`
   259  }