|
|
@ -0,0 +1,146 @@ |
|
|
|
package consensus |
|
|
|
|
|
|
|
import ( |
|
|
|
"strings" |
|
|
|
"sync" |
|
|
|
|
|
|
|
. "github.com/tendermint/tendermint/common" |
|
|
|
sm "github.com/tendermint/tendermint/state" |
|
|
|
"github.com/tendermint/tendermint/types" |
|
|
|
) |
|
|
|
|
|
|
|
type RoundVoteSet struct { |
|
|
|
Prevotes *VoteSet |
|
|
|
Precommits *VoteSet |
|
|
|
} |
|
|
|
|
|
|
|
// Keeps track of VoteSets for all the rounds of a height.
|
|
|
|
// We add the commit votes to all the affected rounds,
|
|
|
|
// and for new rounds carry over the commit set. Commits have
|
|
|
|
// an associated round, so the performance hit won't be O(rounds).
|
|
|
|
type HeightVoteSet struct { |
|
|
|
height uint |
|
|
|
valSet *sm.ValidatorSet |
|
|
|
|
|
|
|
mtx sync.Mutex |
|
|
|
round uint // max tracked round
|
|
|
|
roundVoteSets map[uint]RoundVoteSet // keys: [0...round]
|
|
|
|
commits *VoteSet // all commits for height
|
|
|
|
} |
|
|
|
|
|
|
|
func NewHeightVoteSet(height uint, valSet *sm.ValidatorSet) *HeightVoteSet { |
|
|
|
hvs := &HeightVoteSet{ |
|
|
|
height: height, |
|
|
|
valSet: valSet, |
|
|
|
roundVoteSets: make(map[uint]RoundVoteSet), |
|
|
|
commits: NewVoteSet(height, 0, types.VoteTypeCommit, valSet), |
|
|
|
} |
|
|
|
hvs.SetRound(0) |
|
|
|
return hvs |
|
|
|
} |
|
|
|
|
|
|
|
func (hvs *HeightVoteSet) Height() uint { |
|
|
|
return hvs.height |
|
|
|
} |
|
|
|
|
|
|
|
func (hvs *HeightVoteSet) Round() uint { |
|
|
|
hvs.mtx.Lock() |
|
|
|
defer hvs.mtx.Unlock() |
|
|
|
return hvs.round |
|
|
|
} |
|
|
|
|
|
|
|
// Create more RoundVoteSets up to round with all commits carried over.
|
|
|
|
func (hvs *HeightVoteSet) SetRound(round uint) { |
|
|
|
hvs.mtx.Lock() |
|
|
|
defer hvs.mtx.Unlock() |
|
|
|
if hvs.round != 0 && (round < hvs.round+1) { |
|
|
|
panic("SetRound() must increment hvs.round") |
|
|
|
} |
|
|
|
for r := hvs.round + 1; r <= round; r++ { |
|
|
|
prevotes := NewVoteSet(hvs.height, r, types.VoteTypePrevote, hvs.valSet) |
|
|
|
prevotes.AddFromCommits(hvs.commits) |
|
|
|
precommits := NewVoteSet(hvs.height, r, types.VoteTypePrecommit, hvs.valSet) |
|
|
|
precommits.AddFromCommits(hvs.commits) |
|
|
|
hvs.roundVoteSets[r] = RoundVoteSet{ |
|
|
|
Prevotes: prevotes, |
|
|
|
Precommits: precommits, |
|
|
|
} |
|
|
|
} |
|
|
|
hvs.round = round |
|
|
|
} |
|
|
|
|
|
|
|
// CONTRACT: if err == nil, added == true
|
|
|
|
func (hvs *HeightVoteSet) AddByAddress(address []byte, vote *types.Vote) (added bool, index uint, err error) { |
|
|
|
hvs.mtx.Lock() |
|
|
|
defer hvs.mtx.Unlock() |
|
|
|
voteSet := hvs.getVoteSet(vote.Round, vote.Type) |
|
|
|
if voteSet == nil { |
|
|
|
return |
|
|
|
} |
|
|
|
added, index, err = voteSet.AddByAddress(address, vote) |
|
|
|
if err != nil { |
|
|
|
return |
|
|
|
} |
|
|
|
// If vote is commit, also add to all prevote/precommit for future rounds.
|
|
|
|
if vote.Type == types.VoteTypeCommit { |
|
|
|
for round := vote.Round + 1; round <= hvs.round; round++ { |
|
|
|
voteSet := hvs.getVoteSet(round, types.VoteTypePrevote) |
|
|
|
_, _, err = voteSet.AddByAddress(address, vote) |
|
|
|
if err != nil { |
|
|
|
// TODO slash for prevote after commit
|
|
|
|
log.Warn("Prevote after commit", "address", address, "vote", vote) |
|
|
|
} |
|
|
|
voteSet = hvs.getVoteSet(round, types.VoteTypePrecommit) |
|
|
|
_, _, err = voteSet.AddByAddress(address, vote) |
|
|
|
if err != nil { |
|
|
|
// TODO slash for prevote after commit
|
|
|
|
log.Warn("Prevote after commit", "address", address, "vote", vote) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
func (hvs *HeightVoteSet) GetVoteSet(round uint, type_ byte) *VoteSet { |
|
|
|
hvs.mtx.Lock() |
|
|
|
defer hvs.mtx.Unlock() |
|
|
|
return hvs.getVoteSet(round, type_) |
|
|
|
} |
|
|
|
|
|
|
|
func (hvs *HeightVoteSet) getVoteSet(round uint, type_ byte) *VoteSet { |
|
|
|
if type_ == types.VoteTypeCommit { |
|
|
|
return hvs.commits |
|
|
|
} |
|
|
|
rvs, ok := hvs.roundVoteSets[round] |
|
|
|
if !ok { |
|
|
|
return nil |
|
|
|
} |
|
|
|
switch type_ { |
|
|
|
case types.VoteTypePrevote: |
|
|
|
return rvs.Prevotes |
|
|
|
case types.VoteTypePrecommit: |
|
|
|
return rvs.Precommits |
|
|
|
default: |
|
|
|
panic(Fmt("Unexpected vote type %X", type_)) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
func (hvs *HeightVoteSet) String() string { |
|
|
|
return hvs.StringIndented("") |
|
|
|
} |
|
|
|
|
|
|
|
func (hvs *HeightVoteSet) StringIndented(indent string) string { |
|
|
|
vsStrings := make([]string, 0, hvs.round*2+1) |
|
|
|
vsStrings = append(vsStrings, hvs.commits.StringShort()) |
|
|
|
for round := uint(0); round <= hvs.round; round++ { |
|
|
|
voteSetString := hvs.roundVoteSets[round].Prevotes.StringShort() |
|
|
|
vsStrings = append(vsStrings, voteSetString) |
|
|
|
voteSetString = hvs.roundVoteSets[round].Precommits.StringShort() |
|
|
|
vsStrings = append(vsStrings, voteSetString) |
|
|
|
} |
|
|
|
return Fmt(`HeightVoteSet{H:%v R:0~%v |
|
|
|
%s %v |
|
|
|
%s}`, |
|
|
|
indent, strings.Join(vsStrings, "\n"+indent+" "), |
|
|
|
indent) |
|
|
|
} |