diff --git a/blockchain/store_test.go b/blockchain/store_test.go index 21ee529f..60d4e481 100644 --- a/blockchain/store_test.go +++ b/blockchain/store_test.go @@ -69,8 +69,11 @@ var ( partSet = block.MakePartSet(2) part1 = partSet.GetPart(0) part2 = partSet.GetPart(1) - seenCommit1 = &types.Commit{Precommits: []*types.Vote{{Height: 10, - Timestamp: time.Now().UTC()}}} + seenCommit1 = &types.Commit{ + Precommits: []*types.CommitSig{{ + Timestamp: time.Now().UTC()}}, + HeightNum: 10, + } ) // TODO: This test should be simplified ... @@ -90,8 +93,11 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { // save a block block := makeBlock(bs.Height()+1, state) validPartSet := block.MakePartSet(2) - seenCommit := &types.Commit{Precommits: []*types.Vote{{Height: 10, - Timestamp: time.Now().UTC()}}} + seenCommit := &types.Commit{ + Precommits: []*types.CommitSig{{ + Timestamp: time.Now().UTC()}}, + HeightNum: 10, + } bs.SaveBlock(block, partSet, seenCommit) require.Equal(t, bs.Height(), block.Header.Height, "expecting the new height to be changed") @@ -110,8 +116,11 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { // End of setup, test data - commitAtH10 := &types.Commit{Precommits: []*types.Vote{{Height: 10, - Timestamp: time.Now().UTC()}}} + commitAtH10 := &types.Commit{ + Precommits: []*types.CommitSig{{ + Timestamp: time.Now().UTC()}}, + HeightNum: 10, + } tuples := []struct { block *types.Block parts *types.PartSet @@ -334,8 +343,11 @@ func TestBlockFetchAtHeight(t *testing.T) { block := makeBlock(bs.Height()+1, state) partSet := block.MakePartSet(2) - seenCommit := &types.Commit{Precommits: []*types.Vote{{Height: 10, - Timestamp: time.Now().UTC()}}} + seenCommit := &types.Commit{ + Precommits: []*types.CommitSig{{ + Timestamp: time.Now().UTC()}}, + HeightNum: 10, + } bs.SaveBlock(block, partSet, seenCommit) require.Equal(t, bs.Height(), block.Header.Height, "expecting the new height to be changed") diff --git a/consensus/reactor_test.go b/consensus/reactor_test.go index 4a6d4b9d..702d8051 100644 --- a/consensus/reactor_test.go +++ b/consensus/reactor_test.go @@ -607,12 +607,6 @@ func validateBlock(block *types.Block, activeVals map[string]struct{}) error { if block.LastCommit.Size() != len(activeVals) { return fmt.Errorf("Commit size doesn't match number of active validators. Got %d, expected %d", block.LastCommit.Size(), len(activeVals)) } - - for _, vote := range block.LastCommit.Precommits { - if _, ok := activeVals[string(vote.ValidatorAddress)]; !ok { - return fmt.Errorf("Found vote for unactive validator %X", vote.ValidatorAddress) - } - } return nil } diff --git a/consensus/replay_test.go b/consensus/replay_test.go index 8ea71d35..8121f006 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -531,7 +531,7 @@ func makeBlockchainFromWAL(wal WAL) ([]*types.Block, []*types.Commit, error) { if block.Height != height+1 { panic(fmt.Sprintf("read bad block from wal. got height %d, expected %d", block.Height, height+1)) } - commitHeight := thisBlockCommit.Precommits[0].Height + commitHeight := thisBlockCommit.Height() if commitHeight != height+1 { panic(fmt.Sprintf("commit doesnt match. got height %d, expected %d", commitHeight, height+1)) } @@ -549,8 +549,15 @@ func makeBlockchainFromWAL(wal WAL) ([]*types.Block, []*types.Commit, error) { case *types.Vote: if p.Type == types.VoteTypePrecommit { thisBlockCommit = &types.Commit{ - BlockID: p.BlockID, - Precommits: []*types.Vote{p}, + BlockID: p.BlockID, + Precommits: []*types.CommitSig{ + &types.CommitSig{ + Signature: p.Signature, + Timestamp: p.Timestamp, + }, + }, + HeightNum: p.Height, + RoundNum: p.Round, } } } @@ -564,7 +571,7 @@ func makeBlockchainFromWAL(wal WAL) ([]*types.Block, []*types.Commit, error) { if block.Height != height+1 { panic(fmt.Sprintf("read bad block from wal. got height %d, expected %d", block.Height, height+1)) } - commitHeight := thisBlockCommit.Precommits[0].Height + commitHeight := thisBlockCommit.Height() if commitHeight != height+1 { panic(fmt.Sprintf("commit doesnt match. got height %d, expected %d", commitHeight, height+1)) } diff --git a/consensus/state.go b/consensus/state.go index debe94da..71a49197 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -453,11 +453,14 @@ func (cs *ConsensusState) reconstructLastCommit(state sm.State) { } seenCommit := cs.blockStore.LoadSeenCommit(state.LastBlockHeight) lastPrecommits := types.NewVoteSet(state.ChainID, state.LastBlockHeight, seenCommit.Round(), types.VoteTypePrecommit, state.LastValidators) - for _, precommit := range seenCommit.Precommits { - if precommit == nil { + for idx := 0; idx < len(seenCommit.Precommits); idx++ { + if seenCommit.Precommits[idx] == nil { continue } - added, err := lastPrecommits.AddVote(precommit) + addr, _ := state.LastValidators.GetByIndex(idx) + vote := seenCommit.GetByIndex(idx) + vote.ValidatorAddress = addr + added, err := lastPrecommits.AddVote(vote) if !added || err != nil { cmn.PanicCrisis(fmt.Sprintf("Failed to reconstruct LastCommit: %v", err)) } @@ -1344,7 +1347,7 @@ func (cs *ConsensusState) recordMetrics(height int64, block *types.Block) { missingValidators := 0 missingValidatorsPower := int64(0) for i, val := range cs.Validators.Validators { - var vote *types.Vote + var vote *types.CommitSig if i < len(block.LastCommit.Precommits) { vote = block.LastCommit.Precommits[i] } diff --git a/consensus/types/round_state_test.go b/consensus/types/round_state_test.go index 4d128b18..4a9ddf0b 100644 --- a/consensus/types/round_state_test.go +++ b/consensus/types/round_state_test.go @@ -16,7 +16,7 @@ func BenchmarkRoundStateDeepCopy(b *testing.B) { // Random validators nval, ntxs := 100, 100 vset, _ := types.RandValidatorSet(nval, 1) - precommits := make([]*types.Vote, nval) + precommits := make([]*types.CommitSig, nval) blockID := types.BlockID{ Hash: cmn.RandBytes(20), PartsHeader: types.PartSetHeader{ @@ -25,11 +25,9 @@ func BenchmarkRoundStateDeepCopy(b *testing.B) { } sig := make([]byte, ed25519.SignatureSize) for i := 0; i < nval; i++ { - precommits[i] = &types.Vote{ - ValidatorAddress: types.Address(cmn.RandBytes(20)), - Timestamp: time.Now(), - BlockID: blockID, - Signature: sig, + precommits[i] = &types.CommitSig{ + Timestamp: time.Now(), + Signature: sig, } } txs := make([]types.Tx, ntxs) diff --git a/lite/helpers.go b/lite/helpers.go index 9265aeea..6feecfab 100644 --- a/lite/helpers.go +++ b/lite/helpers.go @@ -71,7 +71,7 @@ func (pkz privKeys) ToValidators(init, inc int64) *types.ValidatorSet { // signHeader properly signs the header with all keys from first to last exclusive. func (pkz privKeys) signHeader(header *types.Header, first, last int) *types.Commit { - votes := make([]*types.Vote, len(pkz)) + cs := make([]*types.CommitSig, len(pkz)) // We need this list to keep the ordering. vset := pkz.ToValidators(1, 0) @@ -79,12 +79,17 @@ func (pkz privKeys) signHeader(header *types.Header, first, last int) *types.Com // Fill in the votes we want. for i := first; i < last && i < len(pkz); i++ { vote := makeVote(header, vset, pkz[i]) - votes[vote.ValidatorIndex] = vote + cs[vote.ValidatorIndex] = &types.CommitSig{ + Signature: vote.Signature, + Timestamp: vote.Timestamp, + } } res := &types.Commit{ BlockID: types.BlockID{Hash: header.Hash()}, - Precommits: votes, + Precommits: cs, + RoundNum: 1, + HeightNum: header.Height, } return res } diff --git a/state/execution.go b/state/execution.go index ab689f5f..b9e01eb2 100644 --- a/state/execution.go +++ b/state/execution.go @@ -241,7 +241,7 @@ func getBeginBlockValidatorInfo(block *types.Block, lastValSet *types.ValidatorS // Collect the vote info (list of validators and whether or not they signed). voteInfos := make([]abci.VoteInfo, len(lastValSet.Validators)) for i, val := range lastValSet.Validators { - var vote *types.Vote + var vote *types.CommitSig if i < len(block.LastCommit.Precommits) { vote = block.LastCommit.Precommits[i] } diff --git a/state/execution_test.go b/state/execution_test.go index 4d7c4f99..6b07f8d6 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -63,17 +63,17 @@ func TestBeginBlockValidators(t *testing.T) { prevBlockID := types.BlockID{prevHash, prevParts} now := time.Now().UTC() - vote0 := &types.Vote{ValidatorIndex: 0, Timestamp: now, Type: types.VoteTypePrecommit} - vote1 := &types.Vote{ValidatorIndex: 1, Timestamp: now} + vote0 := &types.CommitSig{Timestamp: now} + vote1 := &types.CommitSig{Timestamp: now} testCases := []struct { desc string - lastCommitPrecommits []*types.Vote + lastCommitPrecommits []*types.CommitSig expectedAbsentValidators []int }{ - {"none absent", []*types.Vote{vote0, vote1}, []int{}}, - {"one absent", []*types.Vote{vote0, nil}, []int{1}}, - {"multiple absent", []*types.Vote{nil, nil}, []int{0, 1}}, + {"none absent", []*types.CommitSig{vote0, vote1}, []int{}}, + {"one absent", []*types.CommitSig{vote0, nil}, []int{1}}, + {"multiple absent", []*types.CommitSig{nil, nil}, []int{0, 1}}, } for _, tc := range testCases { @@ -133,9 +133,9 @@ func TestBeginBlockByzantineValidators(t *testing.T) { types.TM2PB.Evidence(ev2, valSet, now)}}, } - vote0 := &types.Vote{ValidatorIndex: 0, Timestamp: now, Type: types.VoteTypePrecommit} - vote1 := &types.Vote{ValidatorIndex: 1, Timestamp: now} - votes := []*types.Vote{vote0, vote1} + vote0 := &types.CommitSig{Timestamp: now} + vote1 := &types.CommitSig{Timestamp: now} + votes := []*types.CommitSig{vote0, vote1} lastCommit := &types.Commit{BlockID: prevBlockID, Precommits: votes} for _, tc := range testCases { diff --git a/types/block.go b/types/block.go index 8588d557..fc933203 100644 --- a/types/block.go +++ b/types/block.go @@ -296,49 +296,38 @@ type Commit struct { // 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. - BlockID BlockID `json:"block_id"` - Precommits []*Vote `json:"precommits"` + BlockID BlockID `json:"block_id"` + Precommits []*CommitSig `json:"precommits"` + RoundNum int + HeightNum int64 // Volatile - firstPrecommit *Vote - hash cmn.HexBytes - bitArray *cmn.BitArray + hash cmn.HexBytes + bitArray *cmn.BitArray } -// FirstPrecommit returns the first non-nil precommit in the commit. -// If all precommits are nil, it returns an empty precommit with height 0. -func (commit *Commit) FirstPrecommit() *Vote { - if len(commit.Precommits) == 0 { - return nil - } - if commit.firstPrecommit != nil { - return commit.firstPrecommit - } - for _, precommit := range commit.Precommits { - if precommit != nil { - commit.firstPrecommit = precommit - return precommit - } - } - return &Vote{ - Type: VoteTypePrecommit, - } +type CommitSig struct { + Signature []byte + Timestamp time.Time +} + +func (cs *CommitSig) String(index int, address Address, height int64, round int, blockID BlockID) string { + return fmt.Sprintf("Vote{%v:%X %v/%02d/%v(%v) %X %X @ %s}", + index, cmn.Fingerprint(address), + height, round, VoteTypePrecommit, "Precommit", + cmn.Fingerprint(blockID.Hash), + cmn.Fingerprint(cs.Signature), + CanonicalTime(cs.Timestamp)) } // Height returns the height of the commit func (commit *Commit) Height() int64 { - if len(commit.Precommits) == 0 { - return 0 - } - return commit.FirstPrecommit().Height + return commit.HeightNum } // Round returns the round of the commit func (commit *Commit) Round() int { - if len(commit.Precommits) == 0 { - return 0 - } - return commit.FirstPrecommit().Round + return commit.RoundNum } // Type returns the vote type of the commit, which is always VoteTypePrecommit @@ -368,8 +357,16 @@ func (commit *Commit) BitArray() *cmn.BitArray { } // GetByIndex returns the vote corresponding to a given validator index -func (commit *Commit) GetByIndex(index int) *Vote { - return commit.Precommits[index] +func (com *Commit) GetByIndex(index int) *Vote { + return &Vote{ + ValidatorIndex: index, + Height: com.HeightNum, + Round: com.RoundNum, + Timestamp: com.Precommits[index].Timestamp, + Type: VoteTypePrecommit, + BlockID: com.BlockID, + Signature: com.Precommits[index].Signature, + } } // IsCommit returns true if there is at least one vote @@ -386,30 +383,6 @@ func (commit *Commit) ValidateBasic() error { if len(commit.Precommits) == 0 { return errors.New("No precommits in commit") } - height, round := commit.Height(), commit.Round() - - // Validate the precommits. - for _, precommit := range commit.Precommits { - // It's OK for precommits to be missing. - if precommit == nil { - continue - } - // Ensure that all votes are precommits. - if precommit.Type != VoteTypePrecommit { - return fmt.Errorf("Invalid commit vote. Expected precommit, got %v", - precommit.Type) - } - // Ensure that all heights are the same. - if precommit.Height != height { - return fmt.Errorf("Invalid commit precommit height. Expected %v, got %v", - height, precommit.Height) - } - // Ensure that all rounds are the same. - if precommit.Round != round { - return fmt.Errorf("Invalid commit precommit round. Expected %v, got %v", - round, precommit.Round) - } - } return nil } @@ -435,7 +408,8 @@ func (commit *Commit) StringIndented(indent string) string { } precommitStrings := make([]string, len(commit.Precommits)) for i, precommit := range commit.Precommits { - precommitStrings[i] = precommit.String() + precommitStrings[i] = precommit.String(i, []byte("---"), + commit.HeightNum, commit.RoundNum, commit.BlockID) } return fmt.Sprintf(`Commit{ %s BlockID: %v diff --git a/types/block_test.go b/types/block_test.go index 8e595776..fbba2515 100644 --- a/types/block_test.go +++ b/types/block_test.go @@ -118,7 +118,7 @@ func TestBlockMakePartSetWithEvidence(t *testing.T) { partSet := MakeBlock(h, txs, commit, evList).MakePartSet(1024) assert.NotNil(t, partSet) - assert.Equal(t, 3, partSet.Total()) + assert.Equal(t, 2, partSet.Total()) } func TestBlockHashesTo(t *testing.T) { @@ -193,7 +193,6 @@ func TestCommit(t *testing.T) { commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals) require.NoError(t, err) - assert.NotNil(t, commit.FirstPrecommit()) assert.Equal(t, h-1, commit.Height()) assert.Equal(t, 1, commit.Round()) assert.Equal(t, VoteTypePrecommit, commit.Type()) @@ -204,7 +203,9 @@ func TestCommit(t *testing.T) { require.NotNil(t, commit.BitArray()) assert.Equal(t, cmn.NewBitArray(10).Size(), commit.BitArray().Size()) - assert.Equal(t, voteSet.GetByIndex(0), commit.GetByIndex(0)) + cv := commit.GetByIndex(0) + cv.ValidatorAddress = voteSet.GetByIndex(0).ValidatorAddress + assert.Equal(t, voteSet.GetByIndex(0), cv) assert.True(t, commit.IsCommit()) } @@ -216,21 +217,6 @@ func TestCommitValidateBasic(t *testing.T) { commit = randCommit() commit.Precommits[0] = nil assert.NoError(t, commit.ValidateBasic()) - - // tamper with types - commit = randCommit() - commit.Precommits[0].Type = VoteTypePrevote - assert.Error(t, commit.ValidateBasic()) - - // tamper with height - commit = randCommit() - commit.Precommits[0].Height = int64(100) - assert.Error(t, commit.ValidateBasic()) - - // tamper with round - commit = randCommit() - commit.Precommits[0].Round = 100 - assert.Error(t, commit.ValidateBasic()) } func randCommit() *Commit { diff --git a/types/validator_set.go b/types/validator_set.go index 4dab4d84..50254824 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -270,34 +270,27 @@ func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, height i } talliedVotingPower := int64(0) - round := commit.Round() + + baseVote := Vote{ + Height: height, + Round: commit.Round(), + Type: VoteTypePrecommit, + BlockID: blockID, + } for idx, precommit := range commit.Precommits { if precommit == nil { continue // OK, some precommits can be missing. } - if precommit.Height != height { - return fmt.Errorf("Invalid commit -- wrong height: want %v got %v", height, precommit.Height) - } - if precommit.Round != round { - return fmt.Errorf("Invalid commit -- wrong round: want %v got %v", round, precommit.Round) - } - if precommit.Type != VoteTypePrecommit { - return fmt.Errorf("Invalid commit -- not precommit @ index %v", idx) - } _, val := vals.GetByIndex(idx) + baseVote.Timestamp = precommit.Timestamp // Validate signature. - precommitSignBytes := precommit.SignBytes(chainID) + precommitSignBytes := baseVote.SignBytes(chainID) if !val.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) { return fmt.Errorf("Invalid commit -- invalid signature: %v", precommit) } // Good precommit! - if blockID.Equals(precommit.BlockID) { - talliedVotingPower += val.VotingPower - } else { - // It's OK that the BlockID doesn't match. We include stray - // precommits to measure validator availability. - } + talliedVotingPower += val.VotingPower } if talliedVotingPower > vals.TotalVotingPower()*2/3 { @@ -349,40 +342,33 @@ func (vals *ValidatorSet) VerifyFutureCommit(newSet *ValidatorSet, chainID strin // Check old voting power. oldVotingPower := int64(0) seen := map[int]bool{} - round := commit.Round() + baseVote := Vote{ + Height: height, + Round: commit.Round(), + Type: VoteTypePrecommit, + BlockID: blockID, + } for idx, precommit := range commit.Precommits { if precommit == nil { continue } - if precommit.Height != height { - return cmn.NewError("Blocks don't match - %d vs %d", round, precommit.Round) - } - if precommit.Round != round { - return cmn.NewError("Invalid commit -- wrong round: %v vs %v", round, precommit.Round) - } - if precommit.Type != VoteTypePrecommit { - return cmn.NewError("Invalid commit -- not precommit @ index %v", idx) - } // See if this validator is in oldVals. - idx, val := oldVals.GetByAddress(precommit.ValidatorAddress) + adr, _ := newSet.GetByIndex(idx) + idx, val := oldVals.GetByAddress(adr) if val == nil || seen[idx] { continue // missing or double vote... } seen[idx] = true // Validate signature. - precommitSignBytes := precommit.SignBytes(chainID) + baseVote.Timestamp = precommit.Timestamp + precommitSignBytes := baseVote.SignBytes(chainID) if !val.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) { return cmn.NewError("Invalid commit -- invalid signature: %v", precommit) } // Good precommit! - if blockID.Equals(precommit.BlockID) { - oldVotingPower += val.VotingPower - } else { - // It's OK that the BlockID doesn't match. We include stray - // precommits to measure validator availability. - } + oldVotingPower += val.VotingPower } if oldVotingPower <= oldVals.TotalVotingPower()*2/3 { diff --git a/types/validator_set_test.go b/types/validator_set_test.go index ad9a2b00..39967fc8 100644 --- a/types/validator_set_test.go +++ b/types/validator_set_test.go @@ -392,8 +392,15 @@ func TestValidatorSetVerifyCommit(t *testing.T) { assert.NoError(t, err) vote.Signature = sig commit := &Commit{ - BlockID: blockID, - Precommits: []*Vote{vote}, + BlockID: blockID, + Precommits: []*CommitSig{ + &CommitSig{ + Signature: sig, + Timestamp: vote.Timestamp, + }, + }, + HeightNum: height, + RoundNum: 0, } badChainID := "notmychainID" @@ -401,7 +408,9 @@ func TestValidatorSetVerifyCommit(t *testing.T) { badHeight := height + 1 badCommit := &Commit{ BlockID: blockID, - Precommits: []*Vote{nil}, + Precommits: []*CommitSig{nil}, + HeightNum: height, + RoundNum: 0, } // test some error cases diff --git a/types/vote_set.go b/types/vote_set.go index dbcacbbd..02466ec0 100644 --- a/types/vote_set.go +++ b/types/vote_set.go @@ -543,9 +543,20 @@ func (voteSet *VoteSet) MakeCommit() *Commit { // For every validator, get the precommit votesCopy := make([]*Vote, len(voteSet.votes)) copy(votesCopy, voteSet.votes) + precommits := make([]*CommitSig, len(voteSet.votes)) + for i, v := range votesCopy { + if v != nil { + precommits[i] = &CommitSig{ + Signature: v.Signature, + Timestamp: v.Timestamp, + } + } + } return &Commit{ BlockID: *voteSet.maj23, - Precommits: votesCopy, + Precommits: precommits, + RoundNum: voteSet.round, + HeightNum: voteSet.height, } }