mirror of
https://github.com/fluencelabs/tendermint
synced 2025-05-28 13:41:21 +00:00
consensus/state is 2-step asynchronous
This commit is contained in:
parent
1b95c09160
commit
01b5540ffe
@ -18,9 +18,9 @@ Simple low level store for blocks.
|
||||
There are three types of information stored:
|
||||
- BlockMeta: Meta information about each block
|
||||
- Block part: Parts of each block, aggregated w/ PartSet
|
||||
- Validation: The Validation part of each block, for gossiping commit votes
|
||||
- Validation: The Validation part of each block, for gossiping precommit votes
|
||||
|
||||
Currently the commit signatures are duplicated in the Block parts as
|
||||
Currently the precommit signatures are duplicated in the Block parts as
|
||||
well as the Validation. In the future this may change, perhaps by moving
|
||||
the Validation data outside the Block.
|
||||
*/
|
||||
@ -101,7 +101,7 @@ func (bs *BlockStore) LoadBlockMeta(height uint) *types.BlockMeta {
|
||||
return meta
|
||||
}
|
||||
|
||||
// NOTE: the Commit-vote heights are for the block at `height-1`
|
||||
// NOTE: the Precommit-vote heights are for the block at `height-1`
|
||||
// Since these are included in the subsequent block, the height
|
||||
// is off by 1.
|
||||
func (bs *BlockStore) LoadBlockValidation(height uint) *types.Validation {
|
||||
@ -118,7 +118,7 @@ func (bs *BlockStore) LoadBlockValidation(height uint) *types.Validation {
|
||||
return validation
|
||||
}
|
||||
|
||||
// NOTE: the Commit-vote heights are for the block at `height`
|
||||
// NOTE: the Precommit-vote heights are for the block at `height`
|
||||
func (bs *BlockStore) LoadSeenValidation(height uint) *types.Validation {
|
||||
var n int64
|
||||
var err error
|
||||
@ -134,12 +134,10 @@ func (bs *BlockStore) LoadSeenValidation(height uint) *types.Validation {
|
||||
}
|
||||
|
||||
// blockParts: Must be parts of the block
|
||||
// seenValidation: The +2/3 commits that were seen which finalized the height.
|
||||
// seenValidation: The +2/3 precommits that were seen which committed at height.
|
||||
// If all the nodes restart after committing a block,
|
||||
// we need this to reload the commits to catch-up nodes to the
|
||||
// we need this to reload the precommits to catch-up nodes to the
|
||||
// most recent height. Otherwise they'd stall at H-1.
|
||||
// Also good to have to debug consensus issues & punish wrong-signers
|
||||
// whose commits weren't included in the block.
|
||||
func (bs *BlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, seenValidation *types.Validation) {
|
||||
height := block.Height
|
||||
if height != bs.height+1 {
|
||||
@ -163,7 +161,7 @@ func (bs *BlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, s
|
||||
blockValidationBytes := binary.BinaryBytes(block.Validation)
|
||||
bs.db.Set(calcBlockValidationKey(height), blockValidationBytes)
|
||||
|
||||
// Save seen validation (seen +2/3 commits)
|
||||
// Save seen validation (seen +2/3 precommits for block)
|
||||
seenValidationBytes := binary.BinaryBytes(seenValidation)
|
||||
bs.db.Set(calcSeenValidationKey(height), seenValidationBytes)
|
||||
|
||||
|
@ -15,9 +15,6 @@ type RoundVoteSet struct {
|
||||
}
|
||||
|
||||
// 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
|
||||
@ -25,7 +22,6 @@ type HeightVoteSet struct {
|
||||
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 {
|
||||
@ -33,7 +29,6 @@ func NewHeightVoteSet(height uint, valSet *sm.ValidatorSet) *HeightVoteSet {
|
||||
height: height,
|
||||
valSet: valSet,
|
||||
roundVoteSets: make(map[uint]RoundVoteSet),
|
||||
commits: NewVoteSet(height, 0, types.VoteTypeCommit, valSet),
|
||||
}
|
||||
hvs.SetRound(0)
|
||||
return hvs
|
||||
@ -49,7 +44,7 @@ func (hvs *HeightVoteSet) Round() uint {
|
||||
return hvs.round
|
||||
}
|
||||
|
||||
// Create more RoundVoteSets up to round with all commits carried over.
|
||||
// Create more RoundVoteSets up to round.
|
||||
func (hvs *HeightVoteSet) SetRound(round uint) {
|
||||
hvs.mtx.Lock()
|
||||
defer hvs.mtx.Unlock()
|
||||
@ -58,9 +53,7 @@ func (hvs *HeightVoteSet) SetRound(round uint) {
|
||||
}
|
||||
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,
|
||||
@ -78,39 +71,35 @@ func (hvs *HeightVoteSet) AddByAddress(address []byte, vote *types.Vote) (added
|
||||
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 {
|
||||
func (hvs *HeightVoteSet) Prevotes(round uint) *VoteSet {
|
||||
hvs.mtx.Lock()
|
||||
defer hvs.mtx.Unlock()
|
||||
return hvs.getVoteSet(round, type_)
|
||||
return hvs.getVoteSet(round, types.VoteTypePrevote)
|
||||
}
|
||||
|
||||
func (hvs *HeightVoteSet) Precommits(round uint) *VoteSet {
|
||||
hvs.mtx.Lock()
|
||||
defer hvs.mtx.Unlock()
|
||||
return hvs.getVoteSet(round, types.VoteTypePrecommit)
|
||||
}
|
||||
|
||||
// Last round that has +2/3 prevotes for a particular block or nik.
|
||||
// Returns -1 if no such round exists.
|
||||
func (hvs *HeightVoteSet) POLRound() int {
|
||||
hvs.mtx.Lock()
|
||||
defer hvs.mtx.Unlock()
|
||||
for r := hvs.round; r >= 0; r-- {
|
||||
if hvs.getVoteSet(r, types.VoteTypePrevote).HasTwoThirdsMajority() {
|
||||
return int(r)
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
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
|
||||
@ -130,8 +119,7 @@ func (hvs *HeightVoteSet) String() string {
|
||||
}
|
||||
|
||||
func (hvs *HeightVoteSet) StringIndented(indent string) string {
|
||||
vsStrings := make([]string, 0, hvs.round*2+1)
|
||||
vsStrings = append(vsStrings, hvs.commits.StringShort())
|
||||
vsStrings := make([]string, 0, hvs.round*2)
|
||||
for round := uint(0); round <= hvs.round; round++ {
|
||||
voteSetString := hvs.roundVoteSets[round].Prevotes.StringShort()
|
||||
vsStrings = append(vsStrings, voteSetString)
|
||||
|
101
consensus/pol.go
101
consensus/pol.go
@ -1,101 +0,0 @@
|
||||
package consensus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/tendermint/tendermint/account"
|
||||
"github.com/tendermint/tendermint/binary"
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// Each signature of a POL (proof-of-lock, see whitepaper) is
|
||||
// either a prevote or a commit.
|
||||
// Commits require an additional round which is strictly less than
|
||||
// the POL round. Prevote rounds are equal to the POL round.
|
||||
type POLVoteSignature struct {
|
||||
Round uint `json:"round"`
|
||||
Signature account.SignatureEd25519 `json:"signature"`
|
||||
}
|
||||
|
||||
// Proof of lock.
|
||||
// +2/3 of validators' prevotes for a given blockhash (or nil)
|
||||
type POL struct {
|
||||
Height uint `json:"height"`
|
||||
Round uint `json:"round"`
|
||||
BlockHash []byte `json:"block_hash"` // Could be nil, which makes this a proof of unlock.
|
||||
BlockParts types.PartSetHeader `json:"block_parts"` // When BlockHash is nil, this is zero.
|
||||
Votes []POLVoteSignature `json:"votes"` // Prevote and commit signatures in ValidatorSet order.
|
||||
}
|
||||
|
||||
// Returns whether +2/3 have prevoted/committed for BlockHash.
|
||||
func (pol *POL) Verify(valSet *sm.ValidatorSet) error {
|
||||
|
||||
if uint(len(pol.Votes)) != valSet.Size() {
|
||||
return fmt.Errorf("Invalid POL votes count: Expected %v, got %v",
|
||||
valSet.Size(), len(pol.Votes))
|
||||
}
|
||||
|
||||
talliedVotingPower := uint64(0)
|
||||
prevoteDoc := account.SignBytes(config.GetString("chain_id"), &types.Vote{
|
||||
Height: pol.Height, Round: pol.Round, Type: types.VoteTypePrevote,
|
||||
BlockHash: pol.BlockHash,
|
||||
BlockParts: pol.BlockParts,
|
||||
})
|
||||
seenValidators := map[string]struct{}{}
|
||||
|
||||
for idx, vote := range pol.Votes {
|
||||
// vote may be zero, in which case skip.
|
||||
if vote.Signature.IsZero() {
|
||||
continue
|
||||
}
|
||||
voteDoc := prevoteDoc
|
||||
_, val := valSet.GetByIndex(uint(idx))
|
||||
|
||||
// Commit vote?
|
||||
if vote.Round < pol.Round {
|
||||
voteDoc = account.SignBytes(config.GetString("chain_id"), &types.Vote{
|
||||
Height: pol.Height, Round: vote.Round, Type: types.VoteTypeCommit,
|
||||
BlockHash: pol.BlockHash,
|
||||
BlockParts: pol.BlockParts,
|
||||
})
|
||||
} else if vote.Round > pol.Round {
|
||||
return fmt.Errorf("Invalid commit round %v for POL %v", vote.Round, pol)
|
||||
}
|
||||
|
||||
// Validate
|
||||
if _, seen := seenValidators[string(val.Address)]; seen {
|
||||
return fmt.Errorf("Duplicate validator for vote %v for POL %v", vote, pol)
|
||||
}
|
||||
|
||||
if !val.PubKey.VerifyBytes(voteDoc, vote.Signature) {
|
||||
return fmt.Errorf("Invalid signature for vote %v for POL %v", vote, pol)
|
||||
}
|
||||
|
||||
// Tally
|
||||
seenValidators[string(val.Address)] = struct{}{}
|
||||
talliedVotingPower += val.VotingPower
|
||||
}
|
||||
|
||||
if talliedVotingPower > valSet.TotalVotingPower()*2/3 {
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("Invalid POL, insufficient voting power %v, needed %v",
|
||||
talliedVotingPower, (valSet.TotalVotingPower()*2/3 + 1))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (pol *POL) StringShort() string {
|
||||
if pol == nil {
|
||||
return "nil-POL"
|
||||
} else {
|
||||
return fmt.Sprintf("POL{H:%v R:%v BH:%X}", pol.Height, pol.Round,
|
||||
Fingerprint(pol.BlockHash), pol.BlockParts)
|
||||
}
|
||||
}
|
||||
|
||||
func (pol *POL) MakePartSet() *types.PartSet {
|
||||
return types.NewPartSetFromData(binary.BinaryBytes(pol))
|
||||
}
|
@ -1,213 +0,0 @@
|
||||
package consensus
|
||||
|
||||
import (
|
||||
"github.com/tendermint/tendermint/binary"
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
_ "github.com/tendermint/tendermint/config/tendermint_test"
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// NOTE: see consensus/test.go for common test methods.
|
||||
|
||||
// Convenience method.
|
||||
// Signs the vote and sets the POL's vote at the desired index
|
||||
// Returns the POLVoteSignature pointer, so you can modify it afterwards.
|
||||
func signAddPOLVoteSignature(val *sm.PrivValidator, valSet *sm.ValidatorSet, vote *types.Vote, pol *POL) *POLVoteSignature {
|
||||
vote = vote.Copy()
|
||||
err := val.SignVote(config.GetString("chain_id"), vote)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
idx, _ := valSet.GetByAddress(val.Address) // now we have the index
|
||||
pol.Votes[idx] = POLVoteSignature{vote.Round, vote.Signature}
|
||||
return &pol.Votes[idx]
|
||||
}
|
||||
|
||||
func TestVerifyVotes(t *testing.T) {
|
||||
height, round := uint(1), uint(0)
|
||||
_, valSet, privValidators := randVoteSet(height, round, types.VoteTypePrevote, 10, 1)
|
||||
|
||||
// Make a POL with -2/3 votes.
|
||||
blockHash := RandBytes(32)
|
||||
pol := &POL{
|
||||
Height: height, Round: round, BlockHash: blockHash,
|
||||
Votes: make([]POLVoteSignature, valSet.Size()),
|
||||
}
|
||||
voteProto := &types.Vote{
|
||||
Height: height, Round: round, Type: types.VoteTypePrevote, BlockHash: blockHash,
|
||||
}
|
||||
for i := 0; i < 6; i++ {
|
||||
signAddPOLVoteSignature(privValidators[i], valSet, voteProto, pol)
|
||||
}
|
||||
|
||||
// Check that validation fails.
|
||||
if err := pol.Verify(valSet); err == nil {
|
||||
t.Errorf("Expected POL.Verify() to fail, not enough votes.")
|
||||
}
|
||||
|
||||
// Insert another vote to make +2/3
|
||||
signAddPOLVoteSignature(privValidators[7], valSet, voteProto, pol)
|
||||
|
||||
// Check that validation succeeds.
|
||||
if err := pol.Verify(valSet); err != nil {
|
||||
t.Errorf("POL.Verify() failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyInvalidVote(t *testing.T) {
|
||||
height, round := uint(1), uint(0)
|
||||
_, valSet, privValidators := randVoteSet(height, round, types.VoteTypePrevote, 10, 1)
|
||||
|
||||
// Make a POL with +2/3 votes with the wrong signature.
|
||||
blockHash := RandBytes(32)
|
||||
pol := &POL{
|
||||
Height: height, Round: round, BlockHash: blockHash,
|
||||
Votes: make([]POLVoteSignature, valSet.Size()),
|
||||
}
|
||||
voteProto := &types.Vote{
|
||||
Height: height, Round: round, Type: types.VoteTypePrevote, BlockHash: blockHash,
|
||||
}
|
||||
for i := 0; i < 7; i++ {
|
||||
polVoteSig := signAddPOLVoteSignature(privValidators[i], valSet, voteProto, pol)
|
||||
polVoteSig.Signature[0] += byte(0x01) // mutated!
|
||||
}
|
||||
|
||||
// Check that validation fails.
|
||||
if err := pol.Verify(valSet); err == nil {
|
||||
t.Errorf("Expected POL.Verify() to fail, wrong signatures.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyCommits(t *testing.T) {
|
||||
height, round := uint(1), uint(2)
|
||||
_, valSet, privValidators := randVoteSet(height, round, types.VoteTypePrevote, 10, 1)
|
||||
|
||||
// Make a POL with +2/3 votes.
|
||||
blockHash := RandBytes(32)
|
||||
pol := &POL{
|
||||
Height: height, Round: round, BlockHash: blockHash,
|
||||
Votes: make([]POLVoteSignature, valSet.Size()),
|
||||
}
|
||||
voteProto := &types.Vote{
|
||||
Height: height, Round: round - 1, Type: types.VoteTypeCommit, BlockHash: blockHash,
|
||||
}
|
||||
for i := 0; i < 7; i++ {
|
||||
signAddPOLVoteSignature(privValidators[i], valSet, voteProto, pol)
|
||||
}
|
||||
|
||||
// Check that validation succeeds.
|
||||
if err := pol.Verify(valSet); err != nil {
|
||||
t.Errorf("POL.Verify() failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyInvalidCommits(t *testing.T) {
|
||||
height, round := uint(1), uint(2)
|
||||
_, valSet, privValidators := randVoteSet(height, round, types.VoteTypePrevote, 10, 1)
|
||||
|
||||
// Make a POL with +2/3 votes with the wrong signature.
|
||||
blockHash := RandBytes(32)
|
||||
pol := &POL{
|
||||
Height: height, Round: round, BlockHash: blockHash,
|
||||
Votes: make([]POLVoteSignature, valSet.Size()),
|
||||
}
|
||||
voteProto := &types.Vote{
|
||||
Height: height, Round: round - 1, Type: types.VoteTypeCommit, BlockHash: blockHash,
|
||||
}
|
||||
for i := 0; i < 7; i++ {
|
||||
polVoteSig := signAddPOLVoteSignature(privValidators[i], valSet, voteProto, pol)
|
||||
polVoteSig.Signature[0] += byte(0x01)
|
||||
}
|
||||
|
||||
// Check that validation fails.
|
||||
if err := pol.Verify(valSet); err == nil {
|
||||
t.Errorf("Expected POL.Verify() to fail, wrong signatures.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyInvalidCommitRounds(t *testing.T) {
|
||||
height, round := uint(1), uint(2)
|
||||
_, valSet, privValidators := randVoteSet(height, round, types.VoteTypePrevote, 10, 1)
|
||||
|
||||
// Make a POL with +2/3 commits for the current round.
|
||||
blockHash := RandBytes(32)
|
||||
pol := &POL{
|
||||
Height: height, Round: round, BlockHash: blockHash,
|
||||
Votes: make([]POLVoteSignature, valSet.Size()),
|
||||
}
|
||||
voteProto := &types.Vote{
|
||||
Height: height, Round: round, Type: types.VoteTypeCommit, BlockHash: blockHash,
|
||||
}
|
||||
for i := 0; i < 7; i++ {
|
||||
signAddPOLVoteSignature(privValidators[i], valSet, voteProto, pol)
|
||||
}
|
||||
|
||||
// Check that validation fails.
|
||||
if err := pol.Verify(valSet); err == nil {
|
||||
t.Errorf("Expected POL.Verify() to fail, same round.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyInvalidCommitRounds2(t *testing.T) {
|
||||
height, round := uint(1), uint(2)
|
||||
_, valSet, privValidators := randVoteSet(height, round, types.VoteTypePrevote, 10, 1)
|
||||
|
||||
// Make a POL with +2/3 commits for future round.
|
||||
blockHash := RandBytes(32)
|
||||
pol := &POL{
|
||||
Height: height, Round: round, BlockHash: blockHash,
|
||||
Votes: make([]POLVoteSignature, valSet.Size()),
|
||||
}
|
||||
voteProto := &types.Vote{
|
||||
Height: height, Round: round + 1, Type: types.VoteTypeCommit, BlockHash: blockHash,
|
||||
}
|
||||
for i := 0; i < 7; i++ {
|
||||
polVoteSig := signAddPOLVoteSignature(privValidators[i], valSet, voteProto, pol)
|
||||
polVoteSig.Round += 1 // mutate round
|
||||
}
|
||||
|
||||
// Check that validation fails.
|
||||
if err := pol.Verify(valSet); err == nil {
|
||||
t.Errorf("Expected POL.Verify() to fail, future round.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadWrite(t *testing.T) {
|
||||
height, round := uint(1), uint(2)
|
||||
_, valSet, privValidators := randVoteSet(height, round, types.VoteTypePrevote, 10, 1)
|
||||
|
||||
// Make a POL with +2/3 votes.
|
||||
blockHash := RandBytes(32)
|
||||
pol := &POL{
|
||||
Height: height, Round: round, BlockHash: blockHash,
|
||||
Votes: make([]POLVoteSignature, valSet.Size()),
|
||||
}
|
||||
voteProto := &types.Vote{
|
||||
Height: height, Round: round, Type: types.VoteTypePrevote, BlockHash: blockHash,
|
||||
}
|
||||
for i := 0; i < 7; i++ {
|
||||
signAddPOLVoteSignature(privValidators[i], valSet, voteProto, pol)
|
||||
}
|
||||
|
||||
// Write it to a buffer.
|
||||
buf, n, err := new(bytes.Buffer), new(int64), new(error)
|
||||
binary.WriteBinary(pol, buf, n, err)
|
||||
if *err != nil {
|
||||
t.Fatalf("Failed to write POL: %v", *err)
|
||||
}
|
||||
|
||||
// Read from buffer.
|
||||
pol2 := binary.ReadBinary(&POL{}, buf, n, err).(*POL)
|
||||
if *err != nil {
|
||||
t.Fatalf("Failed to read POL: %v", *err)
|
||||
}
|
||||
|
||||
// Check that validation succeeds.
|
||||
if err := pol2.Verify(valSet); err != nil {
|
||||
t.Errorf("POL.Verify() failed: %v", err)
|
||||
}
|
||||
}
|
1285
consensus/state.go
1285
consensus/state.go
File diff suppressed because it is too large
Load Diff
@ -8,51 +8,6 @@ import (
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
func TestSetupRound(t *testing.T) {
|
||||
cs, privValidators := randConsensusState()
|
||||
val0 := privValidators[0]
|
||||
|
||||
// Add a vote, precommit, and commit by val0.
|
||||
voteTypes := []byte{types.VoteTypePrevote, types.VoteTypePrecommit, types.VoteTypeCommit}
|
||||
for _, voteType := range voteTypes {
|
||||
vote := &types.Vote{Height: 1, Round: 0, Type: voteType} // nil vote
|
||||
err := val0.SignVote(cs.state.ChainID, vote)
|
||||
if err != nil {
|
||||
t.Error("Error signing vote: %v", err)
|
||||
}
|
||||
cs.AddVote(val0.Address, vote)
|
||||
}
|
||||
|
||||
// Ensure that vote appears in RoundState.
|
||||
rs0 := cs.GetRoundState()
|
||||
if vote := rs0.Prevotes.GetByAddress(val0.Address); vote == nil || vote.Type != types.VoteTypePrevote {
|
||||
t.Errorf("Expected to find prevote but got %v", vote)
|
||||
}
|
||||
if vote := rs0.Precommits.GetByAddress(val0.Address); vote == nil || vote.Type != types.VoteTypePrecommit {
|
||||
t.Errorf("Expected to find precommit but got %v", vote)
|
||||
}
|
||||
if vote := rs0.Commits.GetByAddress(val0.Address); vote == nil || vote.Type != types.VoteTypeCommit {
|
||||
t.Errorf("Expected to find commit but got %v", vote)
|
||||
}
|
||||
|
||||
// Setup round 1 (next round)
|
||||
cs.SetupNewRound(1, 1)
|
||||
<-cs.NewStepCh()
|
||||
|
||||
// Now the commit should be copied over to prevotes and precommits.
|
||||
rs1 := cs.GetRoundState()
|
||||
if vote := rs1.Prevotes.GetByAddress(val0.Address); vote == nil || vote.Type != types.VoteTypeCommit {
|
||||
t.Errorf("Expected to find commit but got %v", vote)
|
||||
}
|
||||
if vote := rs1.Precommits.GetByAddress(val0.Address); vote == nil || vote.Type != types.VoteTypeCommit {
|
||||
t.Errorf("Expected to find commit but got %v", vote)
|
||||
}
|
||||
if vote := rs1.Commits.GetByAddress(val0.Address); vote == nil || vote.Type != types.VoteTypeCommit {
|
||||
t.Errorf("Expected to find commit but got %v", vote)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestRunActionProposeNoPrivValidator(t *testing.T) {
|
||||
cs, _ := randConsensusState()
|
||||
cs.RunActionPropose(1, 0)
|
||||
@ -82,128 +37,4 @@ func TestRunActionPropose(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func checkRoundState(t *testing.T, rs *RoundState,
|
||||
height uint, round uint, step RoundStepType) {
|
||||
if rs.Height != height {
|
||||
t.Errorf("rs.Height should be %v, got %v", height, rs.Height)
|
||||
}
|
||||
if rs.Round != round {
|
||||
t.Errorf("rs.Round should be %v, got %v", round, rs.Round)
|
||||
}
|
||||
if rs.Step != step {
|
||||
t.Errorf("rs.Step should be %v, got %v", step, rs.Step)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunActionPrecommitCommitFinalize(t *testing.T) {
|
||||
cs, privValidators := randConsensusState()
|
||||
val0 := privValidators[0]
|
||||
cs.SetPrivValidator(val0)
|
||||
|
||||
cs.RunActionPrecommit(1, 0)
|
||||
<-cs.NewStepCh() // TODO: test this value too.
|
||||
if cs.Precommits.GetByAddress(val0.Address) != nil {
|
||||
t.Errorf("RunActionPrecommit should return nil without a proposal")
|
||||
}
|
||||
|
||||
cs.RunActionPropose(1, 0)
|
||||
<-cs.NewStepCh() // TODO: test this value too.
|
||||
|
||||
cs.RunActionPrecommit(1, 0)
|
||||
<-cs.NewStepCh() // TODO: test this value too.
|
||||
if cs.Precommits.GetByAddress(val0.Address) != nil {
|
||||
t.Errorf("RunActionPrecommit should return nil, not enough prevotes")
|
||||
}
|
||||
|
||||
// Add at least +2/3 prevotes.
|
||||
for i := 0; i < 7; i++ {
|
||||
vote := &types.Vote{
|
||||
Height: 1,
|
||||
Round: 0,
|
||||
Type: types.VoteTypePrevote,
|
||||
BlockHash: cs.ProposalBlock.Hash(),
|
||||
BlockParts: cs.ProposalBlockParts.Header(),
|
||||
}
|
||||
err := privValidators[i].SignVote(cs.state.ChainID, vote)
|
||||
if err != nil {
|
||||
t.Error("Error signing vote: %v", err)
|
||||
}
|
||||
cs.AddVote(privValidators[i].Address, vote)
|
||||
}
|
||||
|
||||
// Test RunActionPrecommit success:
|
||||
cs.RunActionPrecommit(1, 0)
|
||||
<-cs.NewStepCh() // TODO: test this value too.
|
||||
if cs.Precommits.GetByAddress(val0.Address) == nil {
|
||||
t.Errorf("RunActionPrecommit should have succeeded")
|
||||
}
|
||||
checkRoundState(t, cs.GetRoundState(), 1, 0, RoundStepPrecommit)
|
||||
|
||||
// Add at least +2/3 precommits.
|
||||
for i := 0; i < 7; i++ {
|
||||
if bytes.Equal(privValidators[i].Address, val0.Address) {
|
||||
if cs.Precommits.GetByAddress(val0.Address) == nil {
|
||||
t.Errorf("Proposer should already have signed a precommit vote")
|
||||
}
|
||||
continue
|
||||
}
|
||||
vote := &types.Vote{
|
||||
Height: 1,
|
||||
Round: 0,
|
||||
Type: types.VoteTypePrecommit,
|
||||
BlockHash: cs.ProposalBlock.Hash(),
|
||||
BlockParts: cs.ProposalBlockParts.Header(),
|
||||
}
|
||||
err := privValidators[i].SignVote(cs.state.ChainID, vote)
|
||||
if err != nil {
|
||||
t.Error("Error signing vote: %v", err)
|
||||
}
|
||||
added, _, err := cs.AddVote(privValidators[i].Address, vote)
|
||||
if !added || err != nil {
|
||||
t.Errorf("Error adding precommit: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test RunActionCommit success:
|
||||
cs.RunActionCommit(1)
|
||||
<-cs.NewStepCh() // TODO: test this value too.
|
||||
if cs.Commits.GetByAddress(val0.Address) == nil {
|
||||
t.Errorf("RunActionCommit should have succeeded")
|
||||
}
|
||||
checkRoundState(t, cs.GetRoundState(), 1, 0, RoundStepCommit)
|
||||
|
||||
// cs.CommitTime should still be zero
|
||||
if !cs.CommitTime.IsZero() {
|
||||
t.Errorf("Expected CommitTime to yet be zero")
|
||||
}
|
||||
|
||||
// Add at least +2/3 commits.
|
||||
for i := 0; i < 7; i++ {
|
||||
if bytes.Equal(privValidators[i].Address, val0.Address) {
|
||||
if cs.Commits.GetByAddress(val0.Address) == nil {
|
||||
t.Errorf("Proposer should already have signed a commit vote")
|
||||
}
|
||||
continue
|
||||
}
|
||||
vote := &types.Vote{
|
||||
Height: 1,
|
||||
Round: uint(i), // Doesn't matter what round
|
||||
Type: types.VoteTypeCommit,
|
||||
BlockHash: cs.ProposalBlock.Hash(),
|
||||
BlockParts: cs.ProposalBlockParts.Header(),
|
||||
}
|
||||
err := privValidators[i].SignVote(cs.state.ChainID, vote)
|
||||
if err != nil {
|
||||
t.Error("Error signing vote: %v", err)
|
||||
}
|
||||
added, _, err := cs.AddVote(privValidators[i].Address, vote)
|
||||
if !added || err != nil {
|
||||
t.Errorf("Error adding commit: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test TryFinalizeCommit:
|
||||
cs.TryFinalizeCommit(1)
|
||||
<-cs.NewStepCh() // TODO: test this value too.
|
||||
checkRoundState(t, cs.GetRoundState(), 2, 0, RoundStepNewHeight)
|
||||
}
|
||||
// TODO write better consensus state tests
|
||||
|
@ -20,29 +20,28 @@ type Proposal struct {
|
||||
Height uint `json:"height"`
|
||||
Round uint `json:"round"`
|
||||
BlockParts types.PartSetHeader `json:"block_parts"`
|
||||
POLParts types.PartSetHeader `json:"pol_parts"`
|
||||
POLRound int `json:"pol_round"` // -1 if null.
|
||||
Signature account.SignatureEd25519 `json:"signature"`
|
||||
}
|
||||
|
||||
func NewProposal(height uint, round uint, blockParts, polParts types.PartSetHeader) *Proposal {
|
||||
func NewProposal(height uint, round uint, blockParts types.PartSetHeader, polRound int) *Proposal {
|
||||
return &Proposal{
|
||||
Height: height,
|
||||
Round: round,
|
||||
BlockParts: blockParts,
|
||||
POLParts: polParts,
|
||||
POLRound: polRound,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Proposal) String() string {
|
||||
return fmt.Sprintf("Proposal{%v/%v %v %v %v}", p.Height, p.Round,
|
||||
p.BlockParts, p.POLParts, p.Signature)
|
||||
p.BlockParts, p.POLRound, p.Signature)
|
||||
}
|
||||
|
||||
func (p *Proposal) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) {
|
||||
binary.WriteTo([]byte(Fmt(`{"chain_id":"%s"`, chainID)), w, n, err)
|
||||
binary.WriteTo([]byte(`,"proposal":{"block_parts":`), w, n, err)
|
||||
p.BlockParts.WriteSignBytes(w, n, err)
|
||||
binary.WriteTo([]byte(Fmt(`,"height":%v,"pol_parts":`, p.Height)), w, n, err)
|
||||
p.POLParts.WriteSignBytes(w, n, err)
|
||||
binary.WriteTo([]byte(Fmt(`,"height":%v,"pol_round":%v`, p.Height, p.POLRound)), w, n, err)
|
||||
binary.WriteTo([]byte(Fmt(`,"round":%v}}`, p.Round)), w, n, err)
|
||||
}
|
||||
|
@ -39,9 +39,6 @@ func NewVoteSet(height uint, round uint, type_ byte, valSet *sm.ValidatorSet) *V
|
||||
if height == 0 {
|
||||
panic("Cannot make VoteSet for height == 0, doesn't make sense.")
|
||||
}
|
||||
if type_ == types.VoteTypeCommit && round != 0 {
|
||||
panic("Expected round 0 for commit vote set")
|
||||
}
|
||||
return &VoteSet{
|
||||
height: height,
|
||||
round: round,
|
||||
@ -111,10 +108,9 @@ func (voteSet *VoteSet) addByIndex(valIndex uint, vote *types.Vote) (bool, uint,
|
||||
func (voteSet *VoteSet) addVote(val *sm.Validator, valIndex uint, vote *types.Vote) (bool, uint, error) {
|
||||
|
||||
// Make sure the step matches. (or that vote is commit && round < voteSet.round)
|
||||
if vote.Height != voteSet.height ||
|
||||
(vote.Type != types.VoteTypeCommit && vote.Round != voteSet.round) ||
|
||||
(vote.Type != types.VoteTypeCommit && vote.Type != voteSet.type_) ||
|
||||
(vote.Type == types.VoteTypeCommit && voteSet.type_ != types.VoteTypeCommit && vote.Round >= voteSet.round) {
|
||||
if (vote.Height != voteSet.height) ||
|
||||
(vote.Round != voteSet.round) ||
|
||||
(vote.Type != voteSet.type_) {
|
||||
return false, 0, types.ErrVoteUnexpectedStep
|
||||
}
|
||||
|
||||
@ -155,18 +151,6 @@ func (voteSet *VoteSet) addVote(val *sm.Validator, valIndex uint, vote *types.Vo
|
||||
return true, valIndex, nil
|
||||
}
|
||||
|
||||
// Assumes that commits VoteSet is valid.
|
||||
func (voteSet *VoteSet) AddFromCommits(commits *VoteSet) {
|
||||
for valIndex, commit := range commits.votes {
|
||||
if commit == nil {
|
||||
continue
|
||||
}
|
||||
if commit.Round < voteSet.round {
|
||||
voteSet.addByIndex(uint(valIndex), commit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (voteSet *VoteSet) BitArray() *BitArray {
|
||||
if voteSet == nil {
|
||||
return nil
|
||||
@ -201,6 +185,15 @@ func (voteSet *VoteSet) HasTwoThirdsMajority() bool {
|
||||
return voteSet.maj23Exists
|
||||
}
|
||||
|
||||
func (voteSet *VoteSet) HasTwoThirdsAny() bool {
|
||||
if voteSet == nil {
|
||||
return false
|
||||
}
|
||||
voteSet.mtx.Lock()
|
||||
defer voteSet.mtx.Unlock()
|
||||
return voteSet.totalBlockHashVotes > voteSet.valSet.TotalVotingPower()*2/3
|
||||
}
|
||||
|
||||
// Returns either a blockhash (or nil) that received +2/3 majority.
|
||||
// If there exists no such majority, returns (nil, false).
|
||||
func (voteSet *VoteSet) TwoThirdsMajority() (hash []byte, parts types.PartSetHeader, ok bool) {
|
||||
@ -213,50 +206,16 @@ func (voteSet *VoteSet) TwoThirdsMajority() (hash []byte, parts types.PartSetHea
|
||||
}
|
||||
}
|
||||
|
||||
func (voteSet *VoteSet) MakePOL() *POL {
|
||||
if voteSet.type_ != types.VoteTypePrevote {
|
||||
panic("Cannot MakePOL() unless VoteSet.Type is types.VoteTypePrevote")
|
||||
}
|
||||
voteSet.mtx.Lock()
|
||||
defer voteSet.mtx.Unlock()
|
||||
if !voteSet.maj23Exists {
|
||||
return nil
|
||||
}
|
||||
pol := &POL{
|
||||
Height: voteSet.height,
|
||||
Round: voteSet.round,
|
||||
BlockHash: voteSet.maj23Hash,
|
||||
BlockParts: voteSet.maj23Parts,
|
||||
Votes: make([]POLVoteSignature, voteSet.valSet.Size()),
|
||||
}
|
||||
for valIndex, vote := range voteSet.votes {
|
||||
if vote == nil {
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(vote.BlockHash, voteSet.maj23Hash) {
|
||||
continue
|
||||
}
|
||||
if !vote.BlockParts.Equals(voteSet.maj23Parts) {
|
||||
continue
|
||||
}
|
||||
pol.Votes[valIndex] = POLVoteSignature{
|
||||
Round: vote.Round,
|
||||
Signature: vote.Signature,
|
||||
}
|
||||
}
|
||||
return pol
|
||||
}
|
||||
|
||||
func (voteSet *VoteSet) MakeValidation() *types.Validation {
|
||||
if voteSet.type_ != types.VoteTypeCommit {
|
||||
panic("Cannot MakeValidation() unless VoteSet.Type is types.VoteTypeCommit")
|
||||
if voteSet.type_ != types.VoteTypePrecommit {
|
||||
panic("Cannot MakeValidation() unless VoteSet.Type is types.VoteTypePrecommit")
|
||||
}
|
||||
voteSet.mtx.Lock()
|
||||
defer voteSet.mtx.Unlock()
|
||||
if len(voteSet.maj23Hash) == 0 {
|
||||
panic("Cannot MakeValidation() unless a blockhash has +2/3")
|
||||
}
|
||||
commits := make([]types.Commit, voteSet.valSet.Size())
|
||||
precommits := make([]types.Precommit, voteSet.valSet.Size())
|
||||
voteSet.valSet.Iterate(func(valIndex uint, val *sm.Validator) bool {
|
||||
vote := voteSet.votes[valIndex]
|
||||
if vote == nil {
|
||||
@ -268,11 +227,12 @@ func (voteSet *VoteSet) MakeValidation() *types.Validation {
|
||||
if !vote.BlockParts.Equals(voteSet.maj23Parts) {
|
||||
return false
|
||||
}
|
||||
commits[valIndex] = types.Commit{val.Address, vote.Round, vote.Signature}
|
||||
precommits[valIndex] = types.Precommit{val.Address, vote.Signature}
|
||||
return false
|
||||
})
|
||||
return &types.Validation{
|
||||
Commits: commits,
|
||||
Round: voteSet.round,
|
||||
Precommits: precommits,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -225,69 +225,12 @@ func TestBadVotes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddCommitsToPrevoteVotes(t *testing.T) {
|
||||
height, round := uint(2), uint(5)
|
||||
voteSet, _, privValidators := randVoteSet(height, round, types.VoteTypePrevote, 10, 1)
|
||||
|
||||
// val0, val1, val2, val3, val4, val5 vote for nil.
|
||||
vote := &types.Vote{Height: height, Round: round, Type: types.VoteTypePrevote, BlockHash: nil}
|
||||
for i := 0; i < 6; i++ {
|
||||
signAddVote(privValidators[i], vote, voteSet)
|
||||
}
|
||||
hash, header, ok := voteSet.TwoThirdsMajority()
|
||||
if hash != nil || !header.IsZero() || ok {
|
||||
t.Errorf("There should be no 2/3 majority")
|
||||
}
|
||||
|
||||
// Attempt to add a commit from val6 at a previous height
|
||||
vote = &types.Vote{Height: height - 1, Round: round, Type: types.VoteTypeCommit, BlockHash: nil}
|
||||
added, _ := signAddVote(privValidators[6], vote, voteSet)
|
||||
if added {
|
||||
t.Errorf("Expected VoteSet.Add to fail, wrong height.")
|
||||
}
|
||||
|
||||
// Attempt to add a commit from val6 at a later round
|
||||
vote = &types.Vote{Height: height, Round: round + 1, Type: types.VoteTypeCommit, BlockHash: nil}
|
||||
added, _ = signAddVote(privValidators[6], vote, voteSet)
|
||||
if added {
|
||||
t.Errorf("Expected VoteSet.Add to fail, cannot add future round vote.")
|
||||
}
|
||||
|
||||
// Attempt to add a commit from val6 for currrent height/round.
|
||||
vote = &types.Vote{Height: height, Round: round, Type: types.VoteTypeCommit, BlockHash: nil}
|
||||
added, err := signAddVote(privValidators[6], vote, voteSet)
|
||||
if added || err == nil {
|
||||
t.Errorf("Expected VoteSet.Add to fail, only prior round commits can be added.")
|
||||
}
|
||||
|
||||
// Add commit from val6 at a previous round
|
||||
vote = &types.Vote{Height: height, Round: round - 1, Type: types.VoteTypeCommit, BlockHash: nil}
|
||||
added, err = signAddVote(privValidators[6], vote, voteSet)
|
||||
if !added || err != nil {
|
||||
t.Errorf("Expected VoteSet.Add to succeed, commit for prior rounds are relevant.")
|
||||
}
|
||||
|
||||
// Also add commit from val7 for previous round.
|
||||
vote = &types.Vote{Height: height, Round: round - 2, Type: types.VoteTypeCommit, BlockHash: nil}
|
||||
added, err = signAddVote(privValidators[7], vote, voteSet)
|
||||
if !added || err != nil {
|
||||
t.Errorf("Expected VoteSet.Add to succeed. err: %v", err)
|
||||
}
|
||||
|
||||
// We should have 2/3 majority
|
||||
hash, header, ok = voteSet.TwoThirdsMajority()
|
||||
if hash != nil || !header.IsZero() || !ok {
|
||||
t.Errorf("There should be 2/3 majority for nil")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMakeValidation(t *testing.T) {
|
||||
height, round := uint(1), uint(0)
|
||||
voteSet, _, privValidators := randVoteSet(height, round, types.VoteTypeCommit, 10, 1)
|
||||
voteSet, _, privValidators := randVoteSet(height, round, types.VoteTypePrecommit, 10, 1)
|
||||
blockHash, blockParts := CRandBytes(32), types.PartSetHeader{123, CRandBytes(32)}
|
||||
|
||||
vote := &types.Vote{Height: height, Round: round, Type: types.VoteTypeCommit,
|
||||
vote := &types.Vote{Height: height, Round: round, Type: types.VoteTypePrecommit,
|
||||
BlockHash: blockHash, BlockParts: blockParts}
|
||||
|
||||
// 6 out of 10 voted for some block.
|
||||
@ -313,11 +256,11 @@ func TestMakeValidation(t *testing.T) {
|
||||
validation := voteSet.MakeValidation()
|
||||
|
||||
// Validation should have 10 elements
|
||||
if len(validation.Commits) != 10 {
|
||||
t.Errorf("Validation Commits should have the same number of commits as validators")
|
||||
if len(validation.Precommits) != 10 {
|
||||
t.Errorf("Validation Precommits should have the same number of precommits as validators")
|
||||
}
|
||||
|
||||
// Ensure that Validation commits are ordered.
|
||||
// Ensure that Validation precommits are ordered.
|
||||
if err := validation.ValidateBasic(); err != nil {
|
||||
t.Errorf("Error in Validation.ValidateBasic(): %v", err)
|
||||
}
|
||||
|
@ -40,28 +40,28 @@ func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeade
|
||||
|
||||
// Validate block Validation.
|
||||
if block.Height == 1 {
|
||||
if len(block.Validation.Commits) != 0 {
|
||||
return errors.New("Block at height 1 (first block) should have no Validation commits")
|
||||
if len(block.Validation.Precommits) != 0 {
|
||||
return errors.New("Block at height 1 (first block) should have no Validation precommits")
|
||||
}
|
||||
} else {
|
||||
if uint(len(block.Validation.Commits)) != s.LastBondedValidators.Size() {
|
||||
if uint(len(block.Validation.Precommits)) != s.LastBondedValidators.Size() {
|
||||
return errors.New(Fmt("Invalid block validation size. Expected %v, got %v",
|
||||
s.LastBondedValidators.Size(), len(block.Validation.Commits)))
|
||||
s.LastBondedValidators.Size(), len(block.Validation.Precommits)))
|
||||
}
|
||||
var sumVotingPower uint64
|
||||
s.LastBondedValidators.Iterate(func(index uint, val *Validator) bool {
|
||||
commit := block.Validation.Commits[index]
|
||||
if commit.IsZero() {
|
||||
precommit := block.Validation.Precommits[index]
|
||||
if precommit.IsZero() {
|
||||
return false
|
||||
} else {
|
||||
vote := &types.Vote{
|
||||
Height: block.Height - 1,
|
||||
Round: commit.Round,
|
||||
Type: types.VoteTypeCommit,
|
||||
Round: block.Validation.Round,
|
||||
Type: types.VoteTypePrecommit,
|
||||
BlockHash: block.LastBlockHash,
|
||||
BlockParts: block.LastBlockParts,
|
||||
}
|
||||
if val.PubKey.VerifyBytes(account.SignBytes(s.ChainID, vote), commit.Signature) {
|
||||
if val.PubKey.VerifyBytes(account.SignBytes(s.ChainID, vote), precommit.Signature) {
|
||||
sumVotingPower += val.VotingPower
|
||||
return false
|
||||
} else {
|
||||
@ -80,8 +80,8 @@ func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeade
|
||||
}
|
||||
|
||||
// Update Validator.LastCommitHeight as necessary.
|
||||
for i, commit := range block.Validation.Commits {
|
||||
if commit.IsZero() {
|
||||
for i, precommit := range block.Validation.Precommits {
|
||||
if precommit.IsZero() {
|
||||
continue
|
||||
}
|
||||
_, val := s.LastBondedValidators.GetByIndex(uint(i))
|
||||
@ -111,7 +111,7 @@ func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeade
|
||||
// Create BlockCache to cache changes to state.
|
||||
blockCache := NewBlockCache(s)
|
||||
|
||||
// Commit each tx
|
||||
// Execute each tx
|
||||
for _, tx := range block.Data.Txs {
|
||||
err := ExecTx(blockCache, tx, true, s.evc)
|
||||
if err != nil {
|
||||
@ -726,21 +726,14 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea
|
||||
if tx.VoteA.Height != tx.VoteB.Height {
|
||||
return errors.New("DupeoutTx heights don't match")
|
||||
}
|
||||
if tx.VoteA.Type == types.VoteTypeCommit && tx.VoteA.Round < tx.VoteB.Round {
|
||||
// Check special case (not an error, validator must be slashed!)
|
||||
// Validators should not sign another vote after committing.
|
||||
} else if tx.VoteB.Type == types.VoteTypeCommit && tx.VoteB.Round < tx.VoteA.Round {
|
||||
// We need to check both orderings of the votes
|
||||
} else {
|
||||
if tx.VoteA.Round != tx.VoteB.Round {
|
||||
return errors.New("DupeoutTx rounds don't match")
|
||||
}
|
||||
if tx.VoteA.Type != tx.VoteB.Type {
|
||||
return errors.New("DupeoutTx types don't match")
|
||||
}
|
||||
if bytes.Equal(tx.VoteA.BlockHash, tx.VoteB.BlockHash) {
|
||||
return errors.New("DupeoutTx blockhashes shouldn't match")
|
||||
}
|
||||
if tx.VoteA.Round != tx.VoteB.Round {
|
||||
return errors.New("DupeoutTx rounds don't match")
|
||||
}
|
||||
if tx.VoteA.Type != tx.VoteB.Type {
|
||||
return errors.New("DupeoutTx types don't match")
|
||||
}
|
||||
if bytes.Equal(tx.VoteA.BlockHash, tx.VoteB.BlockHash) {
|
||||
return errors.New("DupeoutTx blockhashes shouldn't match")
|
||||
}
|
||||
|
||||
// Good! (Bad validator!)
|
||||
|
@ -1,7 +1,5 @@
|
||||
package state
|
||||
|
||||
// TODO: This logic is crude. Should be more transactional.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
@ -23,7 +21,6 @@ const (
|
||||
stepPropose = 1
|
||||
stepPrevote = 2
|
||||
stepPrecommit = 3
|
||||
stepCommit = 4
|
||||
)
|
||||
|
||||
func voteToStep(vote *types.Vote) uint8 {
|
||||
@ -32,8 +29,6 @@ func voteToStep(vote *types.Vote) uint8 {
|
||||
return stepPrevote
|
||||
case types.VoteTypePrecommit:
|
||||
return stepPrecommit
|
||||
case types.VoteTypeCommit:
|
||||
return stepCommit
|
||||
default:
|
||||
panic("Unknown vote type")
|
||||
}
|
||||
@ -108,7 +103,6 @@ func (privVal *PrivValidator) save() {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: test
|
||||
func (privVal *PrivValidator) SignVote(chainID string, vote *types.Vote) error {
|
||||
privVal.mtx.Lock()
|
||||
defer privVal.mtx.Unlock()
|
||||
@ -119,10 +113,6 @@ func (privVal *PrivValidator) SignVote(chainID string, vote *types.Vote) error {
|
||||
}
|
||||
// More cases for when the height matches
|
||||
if privVal.LastHeight == vote.Height {
|
||||
// If attempting any sign after commit, panic
|
||||
if privVal.LastStep == stepCommit {
|
||||
return errors.New("SignVote on matching height after a commit")
|
||||
}
|
||||
// If round regression, panic
|
||||
if privVal.LastRound > vote.Round {
|
||||
return errors.New("Round regression in SignVote")
|
||||
|
@ -130,7 +130,7 @@ func (s *State) Hash() []byte {
|
||||
}
|
||||
|
||||
// Mutates the block in place and updates it with new state hash.
|
||||
func (s *State) SetBlockStateHash(block *types.Block) error {
|
||||
func (s *State) ComputeBlockStateHash(block *types.Block) error {
|
||||
sCopy := s.Copy()
|
||||
// sCopy has no event cache in it, so this won't fire events
|
||||
err := execBlock(sCopy, block, types.PartSetHeader{})
|
||||
|
@ -71,7 +71,7 @@ func TestCopyState(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func makeBlock(t *testing.T, state *State, commits []types.Commit, txs []types.Tx) *types.Block {
|
||||
func makeBlock(t *testing.T, state *State, validation *types.Validation, txs []types.Tx) *types.Block {
|
||||
block := &types.Block{
|
||||
Header: &types.Header{
|
||||
ChainID: state.ChainID,
|
||||
@ -83,16 +83,14 @@ func makeBlock(t *testing.T, state *State, commits []types.Commit, txs []types.T
|
||||
LastBlockParts: state.LastBlockParts,
|
||||
StateHash: nil,
|
||||
},
|
||||
Validation: &types.Validation{
|
||||
Commits: commits,
|
||||
},
|
||||
Validation: validation,
|
||||
Data: &types.Data{
|
||||
Txs: txs,
|
||||
},
|
||||
}
|
||||
|
||||
// Fill in block StateHash
|
||||
err := state.SetBlockStateHash(block)
|
||||
err := state.ComputeBlockStateHash(block)
|
||||
if err != nil {
|
||||
t.Error("Error appending initial block:", err)
|
||||
}
|
||||
@ -620,21 +618,23 @@ func TestAddValidator(t *testing.T) {
|
||||
|
||||
// The validation for the next block should only require 1 signature
|
||||
// (the new validator wasn't active for block0)
|
||||
commit0 := &types.Vote{
|
||||
precommit0 := &types.Vote{
|
||||
Height: 1,
|
||||
Round: 0,
|
||||
Type: types.VoteTypeCommit,
|
||||
Type: types.VoteTypePrecommit,
|
||||
BlockHash: block0.Hash(),
|
||||
BlockParts: block0Parts.Header(),
|
||||
}
|
||||
privValidators[0].SignVote(s0.ChainID, commit0)
|
||||
privValidators[0].SignVote(s0.ChainID, precommit0)
|
||||
|
||||
block1 := makeBlock(t, s0,
|
||||
[]types.Commit{
|
||||
types.Commit{
|
||||
Address: privValidators[0].Address,
|
||||
Round: 0,
|
||||
Signature: commit0.Signature,
|
||||
types.Validation{
|
||||
Round: 0,
|
||||
Precommits: []types.Precommit{
|
||||
types.Precommit{
|
||||
Address: privValidators[0].Address,
|
||||
Signature: precommit0.Signature,
|
||||
},
|
||||
},
|
||||
}, nil,
|
||||
)
|
||||
|
@ -202,33 +202,33 @@ func (valSet *ValidatorSet) Iterate(fn func(index uint, val *Validator) bool) {
|
||||
|
||||
// Verify that +2/3 of the set had signed the given signBytes
|
||||
func (valSet *ValidatorSet) VerifyValidation(chainID string, hash []byte, parts types.PartSetHeader, height uint, v *types.Validation) error {
|
||||
if valSet.Size() != uint(len(v.Commits)) {
|
||||
if valSet.Size() != uint(len(v.Precommits)) {
|
||||
return errors.New(Fmt("Invalid validation -- wrong set size: %v vs %v",
|
||||
valSet.Size(), len(v.Commits)))
|
||||
valSet.Size(), len(v.Precommits)))
|
||||
}
|
||||
|
||||
talliedVotingPower := uint64(0)
|
||||
seenValidators := map[string]struct{}{}
|
||||
|
||||
for idx, commit := range v.Commits {
|
||||
for idx, precommit := range v.Precommits {
|
||||
// may be zero, in which case skip.
|
||||
if commit.Signature.IsZero() {
|
||||
if precommit.Signature.IsZero() {
|
||||
continue
|
||||
}
|
||||
_, val := valSet.GetByIndex(uint(idx))
|
||||
commitSignBytes := account.SignBytes(chainID, &types.Vote{
|
||||
Height: height, Round: commit.Round, Type: types.VoteTypeCommit,
|
||||
precommitSignBytes := account.SignBytes(chainID, &types.Vote{
|
||||
Height: height, Round: v.Round, Type: types.VoteTypePrecommit,
|
||||
BlockHash: hash,
|
||||
BlockParts: parts,
|
||||
})
|
||||
|
||||
// Validate
|
||||
if _, seen := seenValidators[string(val.Address)]; seen {
|
||||
return fmt.Errorf("Duplicate validator for commit %v for Validation %v", commit, v)
|
||||
return fmt.Errorf("Duplicate validator for precommit %v for Validation %v", precommit, v)
|
||||
}
|
||||
|
||||
if !val.PubKey.VerifyBytes(commitSignBytes, commit.Signature) {
|
||||
return fmt.Errorf("Invalid signature for commit %v for Validation %v", commit, v)
|
||||
if !val.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) {
|
||||
return fmt.Errorf("Invalid signature for precommit %v for Validation %v", precommit, v)
|
||||
}
|
||||
|
||||
// Tally
|
||||
|
@ -176,27 +176,27 @@ func (h *Header) StringIndented(indent string) string {
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
type Commit struct {
|
||||
type Precommit struct {
|
||||
Address []byte `json:"address"`
|
||||
Round uint `json:"round"`
|
||||
Signature account.SignatureEd25519 `json:"signature"`
|
||||
}
|
||||
|
||||
func (commit Commit) IsZero() bool {
|
||||
return commit.Round == 0 && commit.Signature.IsZero()
|
||||
func (pc Precommit) IsZero() bool {
|
||||
return pc.Signature.IsZero()
|
||||
}
|
||||
|
||||
func (commit Commit) String() string {
|
||||
return fmt.Sprintf("Commit{A:%X R:%v %X}", commit.Address, commit.Round, Fingerprint(commit.Signature))
|
||||
func (pc Precommit) String() string {
|
||||
return fmt.Sprintf("Precommit{A:%X %X}", pc.Address, Fingerprint(pc.Signature))
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
// NOTE: The Commits are in order of address to preserve the bonded ValidatorSet order.
|
||||
// Any peer with a block can gossip commits by index with a peer without recalculating the
|
||||
// NOTE: The Precommits are in order of address to preserve the bonded ValidatorSet order.
|
||||
// Any peer with a block can gossip precommits by index with a peer without recalculating the
|
||||
// active ValidatorSet.
|
||||
type Validation struct {
|
||||
Commits []Commit `json:"commits"` // Commits (or nil) of all active validators in address order.
|
||||
Round uint `json:"round"` // Round for all precommits
|
||||
Precommits []Precommit `json:"precommits"` // Precommits (or nil) of all active validators in address order.
|
||||
|
||||
// Volatile
|
||||
hash []byte
|
||||
@ -204,24 +204,24 @@ type Validation struct {
|
||||
}
|
||||
|
||||
func (v *Validation) ValidateBasic() error {
|
||||
if len(v.Commits) == 0 {
|
||||
return errors.New("No commits in validation")
|
||||
if len(v.Precommits) == 0 {
|
||||
return errors.New("No precommits in validation")
|
||||
}
|
||||
lastAddress := []byte{}
|
||||
for i := 0; i < len(v.Commits); i++ {
|
||||
commit := v.Commits[i]
|
||||
if commit.IsZero() {
|
||||
if len(commit.Address) > 0 {
|
||||
return errors.New("Zero commits should not have an address")
|
||||
for i := 0; i < len(v.Precommits); i++ {
|
||||
precommit := v.Precommits[i]
|
||||
if precommit.IsZero() {
|
||||
if len(precommit.Address) > 0 {
|
||||
return errors.New("Zero precommits should not have an address")
|
||||
}
|
||||
} else {
|
||||
if len(commit.Address) == 0 {
|
||||
return errors.New("Nonzero commits should have an address")
|
||||
if len(precommit.Address) == 0 {
|
||||
return errors.New("Nonzero precommits should have an address")
|
||||
}
|
||||
if len(lastAddress) > 0 && bytes.Compare(lastAddress, commit.Address) != -1 {
|
||||
return errors.New("Invalid commit order")
|
||||
if len(lastAddress) > 0 && bytes.Compare(lastAddress, precommit.Address) != -1 {
|
||||
return errors.New("Invalid precommit order")
|
||||
}
|
||||
lastAddress = commit.Address
|
||||
lastAddress = precommit.Address
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -229,9 +229,10 @@ func (v *Validation) ValidateBasic() error {
|
||||
|
||||
func (v *Validation) Hash() []byte {
|
||||
if v.hash == nil {
|
||||
bs := make([]interface{}, len(v.Commits))
|
||||
for i, commit := range v.Commits {
|
||||
bs[i] = commit
|
||||
bs := make([]interface{}, 1+len(v.Precommits))
|
||||
bs[0] = v.Round
|
||||
for i, precommit := range v.Precommits {
|
||||
bs[1+i] = precommit
|
||||
}
|
||||
v.hash = merkle.SimpleHashFromBinaries(bs)
|
||||
}
|
||||
@ -242,22 +243,24 @@ func (v *Validation) StringIndented(indent string) string {
|
||||
if v == nil {
|
||||
return "nil-Validation"
|
||||
}
|
||||
commitStrings := make([]string, len(v.Commits))
|
||||
for i, commit := range v.Commits {
|
||||
commitStrings[i] = commit.String()
|
||||
precommitStrings := make([]string, len(v.Precommits))
|
||||
for i, precommit := range v.Precommits {
|
||||
precommitStrings[i] = precommit.String()
|
||||
}
|
||||
return fmt.Sprintf(`Validation{
|
||||
%s %v
|
||||
%s Round: %v
|
||||
%s Precommits: %v
|
||||
%s}#%X`,
|
||||
indent, strings.Join(commitStrings, "\n"+indent+" "),
|
||||
indent, v.Round,
|
||||
indent, strings.Join(precommitStrings, "\n"+indent+" "),
|
||||
indent, v.hash)
|
||||
}
|
||||
|
||||
func (v *Validation) BitArray() *BitArray {
|
||||
if v.bitArray == nil {
|
||||
v.bitArray = NewBitArray(uint(len(v.Commits)))
|
||||
for i, commit := range v.Commits {
|
||||
v.bitArray.SetIndex(uint(i), !commit.IsZero())
|
||||
v.bitArray = NewBitArray(uint(len(v.Precommits)))
|
||||
for i, precommit := range v.Precommits {
|
||||
v.bitArray.SetIndex(uint(i), !precommit.IsZero())
|
||||
}
|
||||
}
|
||||
return v.bitArray
|
||||
|
@ -27,8 +27,6 @@ func (err *ErrVoteConflictingSignature) Error() string {
|
||||
}
|
||||
|
||||
// Represents a prevote, precommit, or commit vote from validators for consensus.
|
||||
// Commit votes get aggregated into the next block's Validaiton.
|
||||
// See the whitepaper for details.
|
||||
type Vote struct {
|
||||
Height uint `json:"height"`
|
||||
Round uint `json:"round"`
|
||||
@ -42,7 +40,6 @@ type Vote struct {
|
||||
const (
|
||||
VoteTypePrevote = byte(0x01)
|
||||
VoteTypePrecommit = byte(0x02)
|
||||
VoteTypeCommit = byte(0x03)
|
||||
)
|
||||
|
||||
func (vote *Vote) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) {
|
||||
@ -63,8 +60,6 @@ func (vote *Vote) String() string {
|
||||
typeString = "Prevote"
|
||||
case VoteTypePrecommit:
|
||||
typeString = "Precommit"
|
||||
case VoteTypeCommit:
|
||||
typeString = "Commit"
|
||||
default:
|
||||
panic("Unknown vote type")
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user