mirror of
https://github.com/fluencelabs/tendermint
synced 2025-04-24 22:32:15 +00:00
* VoteSignBytes builds CanonicalVote * CommitVotes implements VoteSetReader - new CommitVotes struct holds both the Commit and the ValidatorSet and implements VoteSetReader - ToVote takes a ValidatorSet * fix TestCommit * use CommitSig.BlockID Commits may include votes for a different BlockID, could be nil, or different altogether. This means we can't use `commit.BlockID` for reconstructing the sign bytes, since up to -1/3 of the commits might be for independent BlockIDs. This means CommitSig will need to include an indicator for what BlockID it signed - if it's not the committed one or nil, it will need to include it fully in order to be verified. This is unfortunate but unavoidable so long as we include votes for non-committed BlockIDs (which we do to track validator liveness) * fixes from review * remove CommitVotes. CommitSig contains address * remove commit.canonicalVote method * toVote -> getVote, takes valIdx * update adr-025 * commit.ToVoteSet -> CommitToVoteSet * add test * fix from review
369 lines
11 KiB
Go
369 lines
11 KiB
Go
package types
|
|
|
|
import (
|
|
// it is ok to use math/rand here: we do not need a cryptographically secure random
|
|
// number generator here and we can run the tests a bit faster
|
|
"crypto/rand"
|
|
"math"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/tendermint/tendermint/crypto"
|
|
"github.com/tendermint/tendermint/crypto/tmhash"
|
|
cmn "github.com/tendermint/tendermint/libs/common"
|
|
"github.com/tendermint/tendermint/version"
|
|
)
|
|
|
|
func TestMain(m *testing.M) {
|
|
RegisterMockEvidences(cdc)
|
|
|
|
code := m.Run()
|
|
os.Exit(code)
|
|
}
|
|
|
|
func TestBlockAddEvidence(t *testing.T) {
|
|
txs := []Tx{Tx("foo"), Tx("bar")}
|
|
lastID := makeBlockIDRandom()
|
|
h := int64(3)
|
|
|
|
voteSet, valSet, vals := randVoteSet(h-1, 1, PrecommitType, 10, 1)
|
|
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals)
|
|
require.NoError(t, err)
|
|
|
|
ev := NewMockGoodEvidence(h, 0, valSet.Validators[0].Address)
|
|
evList := []Evidence{ev}
|
|
|
|
block := MakeBlock(h, txs, commit, evList)
|
|
require.NotNil(t, block)
|
|
require.Equal(t, 1, len(block.Evidence.Evidence))
|
|
require.NotNil(t, block.EvidenceHash)
|
|
}
|
|
|
|
func TestBlockValidateBasic(t *testing.T) {
|
|
require.Error(t, (*Block)(nil).ValidateBasic())
|
|
|
|
txs := []Tx{Tx("foo"), Tx("bar")}
|
|
lastID := makeBlockIDRandom()
|
|
h := int64(3)
|
|
|
|
voteSet, valSet, vals := randVoteSet(h-1, 1, PrecommitType, 10, 1)
|
|
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals)
|
|
require.NoError(t, err)
|
|
|
|
ev := NewMockGoodEvidence(h, 0, valSet.Validators[0].Address)
|
|
evList := []Evidence{ev}
|
|
|
|
testCases := []struct {
|
|
testName string
|
|
malleateBlock func(*Block)
|
|
expErr bool
|
|
}{
|
|
{"Make Block", func(blk *Block) {}, false},
|
|
{"Make Block w/ proposer Addr", func(blk *Block) { blk.ProposerAddress = valSet.GetProposer().Address }, false},
|
|
{"Negative Height", func(blk *Block) { blk.Height = -1 }, true},
|
|
{"Increase NumTxs", func(blk *Block) { blk.NumTxs++ }, true},
|
|
{"Remove 1/2 the commits", func(blk *Block) {
|
|
blk.LastCommit.Precommits = commit.Precommits[:commit.Size()/2]
|
|
blk.LastCommit.hash = nil // clear hash or change wont be noticed
|
|
}, true},
|
|
{"Remove LastCommitHash", func(blk *Block) { blk.LastCommitHash = []byte("something else") }, true},
|
|
{"Tampered Data", func(blk *Block) {
|
|
blk.Data.Txs[0] = Tx("something else")
|
|
blk.Data.hash = nil // clear hash or change wont be noticed
|
|
}, true},
|
|
{"Tampered DataHash", func(blk *Block) {
|
|
blk.DataHash = cmn.RandBytes(len(blk.DataHash))
|
|
}, true},
|
|
{"Tampered EvidenceHash", func(blk *Block) {
|
|
blk.EvidenceHash = []byte("something else")
|
|
}, true},
|
|
}
|
|
for i, tc := range testCases {
|
|
t.Run(tc.testName, func(t *testing.T) {
|
|
block := MakeBlock(h, txs, commit, evList)
|
|
block.ProposerAddress = valSet.GetProposer().Address
|
|
tc.malleateBlock(block)
|
|
err = block.ValidateBasic()
|
|
assert.Equal(t, tc.expErr, err != nil, "#%d: %v", i, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBlockHash(t *testing.T) {
|
|
assert.Nil(t, (*Block)(nil).Hash())
|
|
assert.Nil(t, MakeBlock(int64(3), []Tx{Tx("Hello World")}, nil, nil).Hash())
|
|
}
|
|
|
|
func TestBlockMakePartSet(t *testing.T) {
|
|
assert.Nil(t, (*Block)(nil).MakePartSet(2))
|
|
|
|
partSet := MakeBlock(int64(3), []Tx{Tx("Hello World")}, nil, nil).MakePartSet(1024)
|
|
assert.NotNil(t, partSet)
|
|
assert.Equal(t, 1, partSet.Total())
|
|
}
|
|
|
|
func TestBlockMakePartSetWithEvidence(t *testing.T) {
|
|
assert.Nil(t, (*Block)(nil).MakePartSet(2))
|
|
|
|
lastID := makeBlockIDRandom()
|
|
h := int64(3)
|
|
|
|
voteSet, valSet, vals := randVoteSet(h-1, 1, PrecommitType, 10, 1)
|
|
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals)
|
|
require.NoError(t, err)
|
|
|
|
ev := NewMockGoodEvidence(h, 0, valSet.Validators[0].Address)
|
|
evList := []Evidence{ev}
|
|
|
|
partSet := MakeBlock(h, []Tx{Tx("Hello World")}, commit, evList).MakePartSet(1024)
|
|
assert.NotNil(t, partSet)
|
|
assert.Equal(t, 3, partSet.Total())
|
|
}
|
|
|
|
func TestBlockHashesTo(t *testing.T) {
|
|
assert.False(t, (*Block)(nil).HashesTo(nil))
|
|
|
|
lastID := makeBlockIDRandom()
|
|
h := int64(3)
|
|
voteSet, valSet, vals := randVoteSet(h-1, 1, PrecommitType, 10, 1)
|
|
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals)
|
|
require.NoError(t, err)
|
|
|
|
ev := NewMockGoodEvidence(h, 0, valSet.Validators[0].Address)
|
|
evList := []Evidence{ev}
|
|
|
|
block := MakeBlock(h, []Tx{Tx("Hello World")}, commit, evList)
|
|
block.ValidatorsHash = valSet.Hash()
|
|
assert.False(t, block.HashesTo([]byte{}))
|
|
assert.False(t, block.HashesTo([]byte("something else")))
|
|
assert.True(t, block.HashesTo(block.Hash()))
|
|
}
|
|
|
|
func TestBlockSize(t *testing.T) {
|
|
size := MakeBlock(int64(3), []Tx{Tx("Hello World")}, nil, nil).Size()
|
|
if size <= 0 {
|
|
t.Fatal("Size of the block is zero or negative")
|
|
}
|
|
}
|
|
|
|
func TestBlockString(t *testing.T) {
|
|
assert.Equal(t, "nil-Block", (*Block)(nil).String())
|
|
assert.Equal(t, "nil-Block", (*Block)(nil).StringIndented(""))
|
|
assert.Equal(t, "nil-Block", (*Block)(nil).StringShort())
|
|
|
|
block := MakeBlock(int64(3), []Tx{Tx("Hello World")}, nil, nil)
|
|
assert.NotEqual(t, "nil-Block", block.String())
|
|
assert.NotEqual(t, "nil-Block", block.StringIndented(""))
|
|
assert.NotEqual(t, "nil-Block", block.StringShort())
|
|
}
|
|
|
|
func makeBlockIDRandom() BlockID {
|
|
blockHash := make([]byte, tmhash.Size)
|
|
partSetHash := make([]byte, tmhash.Size)
|
|
rand.Read(blockHash) //nolint: gosec
|
|
rand.Read(partSetHash) //nolint: gosec
|
|
blockPartsHeader := PartSetHeader{123, partSetHash}
|
|
return BlockID{blockHash, blockPartsHeader}
|
|
}
|
|
|
|
func makeBlockID(hash []byte, partSetSize int, partSetHash []byte) BlockID {
|
|
return BlockID{
|
|
Hash: hash,
|
|
PartsHeader: PartSetHeader{
|
|
Total: partSetSize,
|
|
Hash: partSetHash,
|
|
},
|
|
}
|
|
|
|
}
|
|
|
|
var nilBytes []byte
|
|
|
|
func TestNilHeaderHashDoesntCrash(t *testing.T) {
|
|
assert.Equal(t, []byte((*Header)(nil).Hash()), nilBytes)
|
|
assert.Equal(t, []byte((new(Header)).Hash()), nilBytes)
|
|
}
|
|
|
|
func TestNilDataHashDoesntCrash(t *testing.T) {
|
|
assert.Equal(t, []byte((*Data)(nil).Hash()), nilBytes)
|
|
assert.Equal(t, []byte(new(Data).Hash()), nilBytes)
|
|
}
|
|
|
|
func TestCommit(t *testing.T) {
|
|
lastID := makeBlockIDRandom()
|
|
h := int64(3)
|
|
voteSet, _, vals := randVoteSet(h-1, 1, PrecommitType, 10, 1)
|
|
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, h-1, commit.Height())
|
|
assert.Equal(t, 1, commit.Round())
|
|
assert.Equal(t, PrecommitType, SignedMsgType(commit.Type()))
|
|
if commit.Size() <= 0 {
|
|
t.Fatalf("commit %v has a zero or negative size: %d", commit, commit.Size())
|
|
}
|
|
|
|
require.NotNil(t, commit.BitArray())
|
|
assert.Equal(t, cmn.NewBitArray(10).Size(), commit.BitArray().Size())
|
|
|
|
assert.Equal(t, voteSet.GetByIndex(0), commit.GetByIndex(0))
|
|
assert.True(t, commit.IsCommit())
|
|
}
|
|
|
|
func TestCommitValidateBasic(t *testing.T) {
|
|
testCases := []struct {
|
|
testName string
|
|
malleateCommit func(*Commit)
|
|
expectErr bool
|
|
}{
|
|
{"Random Commit", func(com *Commit) {}, false},
|
|
{"Nil precommit", func(com *Commit) { com.Precommits[0] = nil }, false},
|
|
{"Incorrect signature", func(com *Commit) { com.Precommits[0].Signature = []byte{0} }, false},
|
|
{"Incorrect type", func(com *Commit) { com.Precommits[0].Type = PrevoteType }, true},
|
|
{"Incorrect height", func(com *Commit) { com.Precommits[0].Height = int64(100) }, true},
|
|
{"Incorrect round", func(com *Commit) { com.Precommits[0].Round = 100 }, true},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.testName, func(t *testing.T) {
|
|
com := randCommit()
|
|
tc.malleateCommit(com)
|
|
assert.Equal(t, tc.expectErr, com.ValidateBasic() != nil, "Validate Basic had an unexpected result")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMaxHeaderBytes(t *testing.T) {
|
|
// Construct a UTF-8 string of MaxChainIDLen length using the supplementary
|
|
// characters.
|
|
// Each supplementary character takes 4 bytes.
|
|
// http://www.i18nguy.com/unicode/supplementary-test.html
|
|
maxChainID := ""
|
|
for i := 0; i < MaxChainIDLen; i++ {
|
|
maxChainID += "𠜎"
|
|
}
|
|
|
|
// time is varint encoded so need to pick the max.
|
|
// year int, month Month, day, hour, min, sec, nsec int, loc *Location
|
|
timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC)
|
|
|
|
h := Header{
|
|
Version: version.Consensus{Block: math.MaxInt64, App: math.MaxInt64},
|
|
ChainID: maxChainID,
|
|
Height: math.MaxInt64,
|
|
Time: timestamp,
|
|
NumTxs: math.MaxInt64,
|
|
TotalTxs: math.MaxInt64,
|
|
LastBlockID: makeBlockID(make([]byte, tmhash.Size), math.MaxInt64, make([]byte, tmhash.Size)),
|
|
LastCommitHash: tmhash.Sum([]byte("last_commit_hash")),
|
|
DataHash: tmhash.Sum([]byte("data_hash")),
|
|
ValidatorsHash: tmhash.Sum([]byte("validators_hash")),
|
|
NextValidatorsHash: tmhash.Sum([]byte("next_validators_hash")),
|
|
ConsensusHash: tmhash.Sum([]byte("consensus_hash")),
|
|
AppHash: tmhash.Sum([]byte("app_hash")),
|
|
LastResultsHash: tmhash.Sum([]byte("last_results_hash")),
|
|
EvidenceHash: tmhash.Sum([]byte("evidence_hash")),
|
|
ProposerAddress: crypto.AddressHash([]byte("proposer_address")),
|
|
}
|
|
|
|
bz, err := cdc.MarshalBinaryLengthPrefixed(h)
|
|
require.NoError(t, err)
|
|
|
|
assert.EqualValues(t, MaxHeaderBytes, len(bz))
|
|
}
|
|
|
|
func randCommit() *Commit {
|
|
lastID := makeBlockIDRandom()
|
|
h := int64(3)
|
|
voteSet, _, vals := randVoteSet(h-1, 1, PrecommitType, 10, 1)
|
|
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return commit
|
|
}
|
|
|
|
func TestBlockMaxDataBytes(t *testing.T) {
|
|
testCases := []struct {
|
|
maxBytes int64
|
|
valsCount int
|
|
evidenceCount int
|
|
panics bool
|
|
result int64
|
|
}{
|
|
0: {-10, 1, 0, true, 0},
|
|
1: {10, 1, 0, true, 0},
|
|
2: {886, 1, 0, true, 0},
|
|
3: {887, 1, 0, false, 0},
|
|
4: {888, 1, 0, false, 1},
|
|
}
|
|
|
|
for i, tc := range testCases {
|
|
if tc.panics {
|
|
assert.Panics(t, func() {
|
|
MaxDataBytes(tc.maxBytes, tc.valsCount, tc.evidenceCount)
|
|
}, "#%v", i)
|
|
} else {
|
|
assert.Equal(t,
|
|
tc.result,
|
|
MaxDataBytes(tc.maxBytes, tc.valsCount, tc.evidenceCount),
|
|
"#%v", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBlockMaxDataBytesUnknownEvidence(t *testing.T) {
|
|
testCases := []struct {
|
|
maxBytes int64
|
|
valsCount int
|
|
panics bool
|
|
result int64
|
|
}{
|
|
0: {-10, 1, true, 0},
|
|
1: {10, 1, true, 0},
|
|
2: {984, 1, true, 0},
|
|
3: {985, 1, false, 0},
|
|
4: {986, 1, false, 1},
|
|
}
|
|
|
|
for i, tc := range testCases {
|
|
if tc.panics {
|
|
assert.Panics(t, func() {
|
|
MaxDataBytesUnknownEvidence(tc.maxBytes, tc.valsCount)
|
|
}, "#%v", i)
|
|
} else {
|
|
assert.Equal(t,
|
|
tc.result,
|
|
MaxDataBytesUnknownEvidence(tc.maxBytes, tc.valsCount),
|
|
"#%v", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCommitToVoteSet(t *testing.T) {
|
|
lastID := makeBlockIDRandom()
|
|
h := int64(3)
|
|
|
|
voteSet, valSet, vals := randVoteSet(h-1, 1, PrecommitType, 10, 1)
|
|
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals)
|
|
assert.NoError(t, err)
|
|
|
|
chainID := voteSet.ChainID()
|
|
voteSet2 := CommitToVoteSet(chainID, commit, valSet)
|
|
|
|
for i := 0; i < len(vals); i++ {
|
|
vote1 := voteSet.GetByIndex(i)
|
|
vote2 := voteSet2.GetByIndex(i)
|
|
vote3 := commit.GetVote(i)
|
|
|
|
vote1bz := cdc.MustMarshalBinaryBare(vote1)
|
|
vote2bz := cdc.MustMarshalBinaryBare(vote2)
|
|
vote3bz := cdc.MustMarshalBinaryBare(vote3)
|
|
assert.Equal(t, vote1bz, vote2bz)
|
|
assert.Equal(t, vote1bz, vote3bz)
|
|
}
|
|
}
|