Skip to content

Commit

Permalink
update consensus 2/3 to 1/2
Browse files Browse the repository at this point in the history
  • Loading branch information
Ro0r committed Sep 7, 2023
1 parent 214f7fe commit ec115ac
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 51 deletions.
2 changes: 1 addition & 1 deletion consensus/reactor.go
Original file line number Diff line number Diff line change
Expand Up @@ -1453,7 +1453,7 @@ func (m *NewRoundStepMessage) String() string {
//-------------------------------------

// NewValidBlockMessage is sent when a validator observes a valid block B in some round r,
//i.e., there is a Proposal for block B and 2/3+ prevotes for the block B in the round r.
// i.e., there is a Proposal for block B and 2/3+ prevotes for the block B in the round r.
// In case the block is also committed, then IsCommit flag is set to true.
type NewValidBlockMessage struct {
Height int64
Expand Down
8 changes: 6 additions & 2 deletions consensus/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -819,7 +819,9 @@ func (cs *State) handleTxsAvailable() {
// Used internally by handleTimeout and handleMsg to make state transitions

// Enter: `timeoutNewHeight` by startTime (commitTime+timeoutCommit),
// or, if SkipTimeoutCommit==true, after receiving all precommits from (height,round-1)
//
// or, if SkipTimeoutCommit==true, after receiving all precommits from (height,round-1)
//
// Enter: `timeoutPrecommits` after any +2/3 precommits from (height,round-1)
// Enter: +2/3 precommits for nil at (height,round-1)
// Enter: +2/3 prevotes any or +2/3 precommits for block or any from (height, round)
Expand Down Expand Up @@ -902,7 +904,9 @@ func (cs *State) needProofBlock(height int64) bool {

// Enter (CreateEmptyBlocks): from enterNewRound(height,round)
// Enter (CreateEmptyBlocks, CreateEmptyBlocksInterval > 0 ):
// after enterNewRound(height,round), after timeout of CreateEmptyBlocksInterval
//
// after enterNewRound(height,round), after timeout of CreateEmptyBlocksInterval
//
// Enter (!CreateEmptyBlocks) : after enterNewRound(height,round), once txs are in the mempool
func (cs *State) enterPropose(height int64, round int) {
logger := cs.Logger.With("height", height, "round", round)
Expand Down
37 changes: 21 additions & 16 deletions types/validator_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,14 +383,17 @@ func processChanges(origChanges []*Validator) (updates, removals []*Validator, e
//
// Inputs:
// updates - a list of proper validator changes, i.e. they have been verified by processChanges for duplicates
// and invalid values.
//
// and invalid values.
//
// vals - the original validator set. Note that vals is NOT modified by this function.
// removedPower - the total voting power that will be removed after the updates are verified and applied.
//
// Returns:
// tvpAfterUpdatesBeforeRemovals - the new total voting power if these updates would be applied without the removals.
// Note that this will be < 2 * MaxTotalVotingPower in case high power validators are removed and
// validators are added/ updated with high power values.
//
// Note that this will be < 2 * MaxTotalVotingPower in case high power validators are removed and
// validators are added/ updated with high power values.
//
// err - non-nil if the maximum allowed total voting power would be exceeded
func verifyUpdates(
Expand Down Expand Up @@ -441,8 +444,9 @@ func numNewValidators(updates []*Validator, vals *ValidatorSet) int {
// 'updates' parameter must be a list of unique validators to be added or updated.
//
// 'updatedTotalVotingPower' is the total voting power of a set where all updates would be applied but
// not the removals. It must be < 2*MaxTotalVotingPower and may be close to this limit if close to
// MaxTotalVotingPower will be removed. This is still safe from overflow since MaxTotalVotingPower is maxInt64/8.
//
// not the removals. It must be < 2*MaxTotalVotingPower and may be close to this limit if close to
// MaxTotalVotingPower will be removed. This is still safe from overflow since MaxTotalVotingPower is maxInt64/8.
//
// No changes are made to the validator set 'vals'.
func computeNewPriorities(updates []*Validator, vals *ValidatorSet, updatedTotalVotingPower int64) {
Expand Down Expand Up @@ -612,14 +616,15 @@ func (vals *ValidatorSet) updateWithChangeSet(changes []*Validator, allowDeletes

// UpdateWithChangeSet attempts to update the validator set with 'changes'.
// It performs the following steps:
// - validates the changes making sure there are no duplicates and splits them in updates and deletes
// - verifies that applying the changes will not result in errors
// - computes the total voting power BEFORE removals to ensure that in the next steps the priorities
// across old and newly added validators are fair
// - computes the priorities of new validators against the final set
// - applies the updates against the validator set
// - applies the removals against the validator set
// - performs scaling and centering of priority values
// - validates the changes making sure there are no duplicates and splits them in updates and deletes
// - verifies that applying the changes will not result in errors
// - computes the total voting power BEFORE removals to ensure that in the next steps the priorities
// across old and newly added validators are fair
// - computes the priorities of new validators against the final set
// - applies the updates against the validator set
// - applies the removals against the validator set
// - performs scaling and centering of priority values
//
// If an error is detected during verification steps, it is returned and the validator set
// is not changed.
func (vals *ValidatorSet) UpdateWithChangeSet(changes []*Validator) error {
Expand All @@ -644,7 +649,7 @@ func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID,
}

talliedVotingPower := int64(0)
votingPowerNeeded := vals.TotalVotingPower() * 2 / 3
votingPowerNeeded := vals.TotalVotingPower() * 1 / 2
for idx, commitSig := range commit.Signatures {
if commitSig.Absent() {
continue // OK, some signatures can be absent.
Expand Down Expand Up @@ -701,7 +706,7 @@ func (vals *ValidatorSet) VerifyCommitLight(chainID string, blockID BlockID,
}

talliedVotingPower := int64(0)
votingPowerNeeded := vals.TotalVotingPower() * 2 / 3
votingPowerNeeded := vals.TotalVotingPower() * 1 / 2
for idx, commitSig := range commit.Signatures {
// No need to verify absent or nil votes.
if !commitSig.ForBlock() {
Expand Down Expand Up @@ -799,7 +804,7 @@ func (vals *ValidatorSet) VerifyFutureCommit(newSet *ValidatorSet, chainID strin
// }
}

if got, needed := oldVotingPower, oldVals.TotalVotingPower()*2/3; got <= needed {
if got, needed := oldVotingPower, oldVals.TotalVotingPower()*1/2; got <= needed {
return ErrNotEnoughVotingPowerSigned{Got: got, Needed: needed}
}
return nil
Expand Down
66 changes: 34 additions & 32 deletions types/vote_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,38 +25,38 @@ const (
type P2PID string

/*
VoteSet helps collect signatures from validators at each height+round for a
predefined vote type.
VoteSet helps collect signatures from validators at each height+round for a
predefined vote type.
We need VoteSet to be able to keep track of conflicting votes when validators
double-sign. Yet, we can't keep track of *all* the votes seen, as that could
be a DoS attack vector.
We need VoteSet to be able to keep track of conflicting votes when validators
double-sign. Yet, we can't keep track of *all* the votes seen, as that could
be a DoS attack vector.
There are two storage areas for votes.
1. voteSet.votes
2. voteSet.votesByBlock
There are two storage areas for votes.
1. voteSet.votes
2. voteSet.votesByBlock
`.votes` is the "canonical" list of votes. It always has at least one vote,
if a vote from a validator had been seen at all. Usually it keeps track of
the first vote seen, but when a 2/3 majority is found, votes for that get
priority and are copied over from `.votesByBlock`.
`.votes` is the "canonical" list of votes. It always has at least one vote,
if a vote from a validator had been seen at all. Usually it keeps track of
the first vote seen, but when a 2/3 majority is found, votes for that get
priority and are copied over from `.votesByBlock`.
`.votesByBlock` keeps track of a list of votes for a particular block. There
are two ways a &blockVotes{} gets created in `.votesByBlock`.
1. the first vote seen by a validator was for the particular block.
2. a peer claims to have seen 2/3 majority for the particular block.
`.votesByBlock` keeps track of a list of votes for a particular block. There
are two ways a &blockVotes{} gets created in `.votesByBlock`.
1. the first vote seen by a validator was for the particular block.
2. a peer claims to have seen 2/3 majority for the particular block.
Since the first vote from a validator will always get added in `.votesByBlock`
, all votes in `.votes` will have a corresponding entry in `.votesByBlock`.
Since the first vote from a validator will always get added in `.votesByBlock`
, all votes in `.votes` will have a corresponding entry in `.votesByBlock`.
When a &blockVotes{} in `.votesByBlock` reaches a 2/3 majority quorum, its
votes are copied into `.votes`.
When a &blockVotes{} in `.votesByBlock` reaches a 2/3 majority quorum, its
votes are copied into `.votes`.
All this is memory bounded because conflicting votes only get added if a peer
told us to track that block, each peer only gets to tell us 1 such block, and,
there's only a limited number of peers.
All this is memory bounded because conflicting votes only get added if a peer
told us to track that block, each peer only gets to tell us 1 such block, and,
there's only a limited number of peers.
NOTE: Assumes that the sum total of voting power does not exceed MaxUInt64.
NOTE: Assumes that the sum total of voting power does not exceed MaxUInt64.
*/
type VoteSet struct {
chainID string
Expand Down Expand Up @@ -132,8 +132,10 @@ func (voteSet *VoteSet) Size() int {

// Returns added=true if vote is valid and new.
// Otherwise returns err=ErrVote[
// UnexpectedStep | InvalidIndex | InvalidAddress |
// InvalidSignature | InvalidBlockHash | ConflictingVotes ]
//
// UnexpectedStep | InvalidIndex | InvalidAddress |
// InvalidSignature | InvalidBlockHash | ConflictingVotes ]
//
// Duplicate votes return added=false, err=nil.
// Conflicting votes return added=*, err=ErrVoteConflictingVotes.
// NOTE: vote should not be mutated after adding.
Expand Down Expand Up @@ -276,7 +278,7 @@ func (voteSet *VoteSet) addVerifiedVote(

// Before adding to votesByBlock, see if we'll exceed quorum
origSum := votesByBlock.sum
quorum := voteSet.valSet.TotalVotingPower()*2/3 + 1
quorum := voteSet.valSet.TotalVotingPower()*1/2 + 1

// Add vote to votesByBlock
votesByBlock.addVerifiedVote(vote, votingPower)
Expand Down Expand Up @@ -414,7 +416,7 @@ func (voteSet *VoteSet) HasTwoThirdsAny() bool {
}
voteSet.mtx.Lock()
defer voteSet.mtx.Unlock()
return voteSet.sum > voteSet.valSet.TotalVotingPower()*2/3
return voteSet.sum > voteSet.valSet.TotalVotingPower()*1/2
}

func (voteSet *VoteSet) HasAll() bool {
Expand Down Expand Up @@ -581,10 +583,10 @@ func (voteSet *VoteSet) MakeCommit() *Commit {
//--------------------------------------------------------------------------------

/*
Votes for a particular block
There are two ways a *blockVotes gets created for a blockKey.
1. first (non-conflicting) vote of a validator w/ blockKey (peerMaj23=false)
2. A peer claims to have a 2/3 majority w/ blockKey (peerMaj23=true)
Votes for a particular block
There are two ways a *blockVotes gets created for a blockKey.
1. first (non-conflicting) vote of a validator w/ blockKey (peerMaj23=false)
2. A peer claims to have a 2/3 majority w/ blockKey (peerMaj23=true)
*/
type blockVotes struct {
peerMaj23 bool // peer claims to have maj23
Expand Down

0 comments on commit ec115ac

Please sign in to comment.