github.com/line/ostracon@v1.0.10-0.20230328032236-7f20145f065d/consensus/types/height_vote_set.go (about)

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