github.com/vipernet-xyz/tm@v0.34.24/consensus/types/height_vote_set.go (about)

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