You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

261 lines
7.4 KiB

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