You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

377 lines
12 KiB

package state_test
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
dbm "github.com/tendermint/tm-db"
abciclient "github.com/tendermint/tendermint/abci/client"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/tendermint/tendermint/crypto/tmhash"
"github.com/tendermint/tendermint/internal/eventbus"
mpmocks "github.com/tendermint/tendermint/internal/mempool/mocks"
"github.com/tendermint/tendermint/internal/proxy"
sm "github.com/tendermint/tendermint/internal/state"
"github.com/tendermint/tendermint/internal/state/mocks"
statefactory "github.com/tendermint/tendermint/internal/state/test/factory"
"github.com/tendermint/tendermint/internal/store"
testfactory "github.com/tendermint/tendermint/internal/test/factory"
"github.com/tendermint/tendermint/libs/log"
tmtime "github.com/tendermint/tendermint/libs/time"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
"github.com/tendermint/tendermint/types"
)
const validationTestsStopHeight int64 = 10
func TestValidateBlockHeader(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
logger := log.TestingLogger()
proxyApp := proxy.New(abciclient.NewLocalClient(logger, &testApp{}), logger, proxy.NopMetrics())
require.NoError(t, proxyApp.Start(ctx))
eventBus := eventbus.NewDefault(logger)
require.NoError(t, eventBus.Start(ctx))
state, stateDB, privVals := makeState(t, 3, 1)
stateStore := sm.NewStore(stateDB)
mp := &mpmocks.Mempool{}
mp.On("Lock").Return()
mp.On("Unlock").Return()
mp.On("FlushAppConn", mock.Anything).Return(nil)
mp.On("Update",
mock.Anything,
mock.Anything,
mock.Anything,
mock.Anything,
mock.Anything,
mock.Anything).Return(nil)
blockStore := store.NewBlockStore(dbm.NewMemDB())
blockExec := sm.NewBlockExecutor(
stateStore,
logger,
proxyApp,
mp,
sm.EmptyEvidencePool{},
blockStore,
eventBus,
)
lastCommit := types.NewCommit(0, 0, types.BlockID{}, nil)
// some bad values
wrongHash := tmhash.Sum([]byte("this hash is wrong"))
wrongVersion1 := state.Version.Consensus
wrongVersion1.Block += 2
wrongVersion2 := state.Version.Consensus
wrongVersion2.App += 2
// Manipulation of any header field causes failure.
testCases := []struct {
name string
malleateBlock func(block *types.Block)
}{
{"Version wrong1", func(block *types.Block) { block.Version = wrongVersion1 }},
{"Version wrong2", func(block *types.Block) { block.Version = wrongVersion2 }},
{"ChainID wrong", func(block *types.Block) { block.ChainID = "not-the-real-one" }},
{"Height wrong", func(block *types.Block) { block.Height += 10 }},
{"Time wrong", func(block *types.Block) { block.Time = block.Time.Add(-time.Second * 1) }},
{"LastBlockID wrong", func(block *types.Block) { block.LastBlockID.PartSetHeader.Total += 10 }},
{"LastCommitHash wrong", func(block *types.Block) { block.LastCommitHash = wrongHash }},
{"DataHash wrong", func(block *types.Block) { block.DataHash = wrongHash }},
{"ValidatorsHash wrong", func(block *types.Block) { block.ValidatorsHash = wrongHash }},
{"NextValidatorsHash wrong", func(block *types.Block) { block.NextValidatorsHash = wrongHash }},
{"ConsensusHash wrong", func(block *types.Block) { block.ConsensusHash = wrongHash }},
{"AppHash wrong", func(block *types.Block) { block.AppHash = wrongHash }},
{"LastResultsHash wrong", func(block *types.Block) { block.LastResultsHash = wrongHash }},
{"EvidenceHash wrong", func(block *types.Block) { block.EvidenceHash = wrongHash }},
{"Proposer wrong", func(block *types.Block) { block.ProposerAddress = ed25519.GenPrivKey().PubKey().Address() }},
{"Proposer invalid", func(block *types.Block) { block.ProposerAddress = []byte("wrong size") }},
{"first LastCommit contains signatures", func(block *types.Block) {
block.LastCommit = types.NewCommit(0, 0, types.BlockID{}, []types.CommitSig{types.NewCommitSigAbsent()})
block.LastCommitHash = block.LastCommit.Hash()
}},
}
// Build up state for multiple heights
for height := int64(1); height < validationTestsStopHeight; height++ {
/*
Invalid blocks don't pass
*/
for _, tc := range testCases {
block := statefactory.MakeBlock(state, height, lastCommit)
tc.malleateBlock(block)
err := blockExec.ValidateBlock(ctx, state, block)
t.Logf("%s: %v", tc.name, err)
require.Error(t, err, tc.name)
}
/*
A good block passes
*/
state, _, lastCommit = makeAndCommitGoodBlock(ctx, t,
state, height, lastCommit, state.Validators.GetProposer().Address, blockExec, privVals, nil)
}
nextHeight := validationTestsStopHeight
block := statefactory.MakeBlock(state, nextHeight, lastCommit)
state.InitialHeight = nextHeight + 1
err := blockExec.ValidateBlock(ctx, state, block)
require.Error(t, err, "expected an error when state is ahead of block")
assert.Contains(t, err.Error(), "lower than initial height")
}
func TestValidateBlockCommit(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
logger := log.TestingLogger()
proxyApp := proxy.New(abciclient.NewLocalClient(logger, &testApp{}), logger, proxy.NopMetrics())
require.NoError(t, proxyApp.Start(ctx))
eventBus := eventbus.NewDefault(logger)
require.NoError(t, eventBus.Start(ctx))
state, stateDB, privVals := makeState(t, 1, 1)
stateStore := sm.NewStore(stateDB)
mp := &mpmocks.Mempool{}
mp.On("Lock").Return()
mp.On("Unlock").Return()
mp.On("FlushAppConn", mock.Anything).Return(nil)
mp.On("Update",
mock.Anything,
mock.Anything,
mock.Anything,
mock.Anything,
mock.Anything,
mock.Anything).Return(nil)
blockStore := store.NewBlockStore(dbm.NewMemDB())
blockExec := sm.NewBlockExecutor(
stateStore,
logger,
proxyApp,
mp,
sm.EmptyEvidencePool{},
blockStore,
eventBus,
)
lastCommit := types.NewCommit(0, 0, types.BlockID{}, nil)
wrongSigsCommit := types.NewCommit(1, 0, types.BlockID{}, nil)
badPrivVal := types.NewMockPV()
for height := int64(1); height < validationTestsStopHeight; height++ {
proposerAddr := state.Validators.GetProposer().Address
if height > 1 {
/*
#2589: ensure state.LastValidators.VerifyCommit fails here
*/
// should be height-1 instead of height
wrongHeightVote, err := testfactory.MakeVote(
ctx,
privVals[proposerAddr.String()],
chainID,
1,
height,
0,
2,
state.LastBlockID,
time.Now(),
)
require.NoError(t, err)
wrongHeightCommit := types.NewCommit(
wrongHeightVote.Height,
wrongHeightVote.Round,
state.LastBlockID,
[]types.CommitSig{wrongHeightVote.CommitSig()},
)
block := statefactory.MakeBlock(state, height, wrongHeightCommit)
err = blockExec.ValidateBlock(ctx, state, block)
_, isErrInvalidCommitHeight := err.(types.ErrInvalidCommitHeight)
require.True(t, isErrInvalidCommitHeight, "expected ErrInvalidCommitHeight at height %d but got: %v", height, err)
/*
#2589: test len(block.LastCommit.Signatures) == state.LastValidators.Size()
*/
block = statefactory.MakeBlock(state, height, wrongSigsCommit)
err = blockExec.ValidateBlock(ctx, state, block)
_, isErrInvalidCommitSignatures := err.(types.ErrInvalidCommitSignatures)
require.True(t, isErrInvalidCommitSignatures,
"expected ErrInvalidCommitSignatures at height %d, but got: %v",
height,
err,
)
}
/*
A good block passes
*/
var blockID types.BlockID
state, blockID, lastCommit = makeAndCommitGoodBlock(
ctx,
t,
state,
height,
lastCommit,
proposerAddr,
blockExec,
privVals,
nil,
)
/*
wrongSigsCommit is fine except for the extra bad precommit
*/
goodVote, err := testfactory.MakeVote(
ctx,
privVals[proposerAddr.String()],
chainID,
1,
height,
0,
2,
blockID,
time.Now(),
)
require.NoError(t, err)
bpvPubKey, err := badPrivVal.GetPubKey(ctx)
require.NoError(t, err)
badVote := &types.Vote{
ValidatorAddress: bpvPubKey.Address(),
ValidatorIndex: 0,
Height: height,
Round: 0,
Timestamp: tmtime.Now(),
Type: tmproto.PrecommitType,
BlockID: blockID,
}
g := goodVote.ToProto()
b := badVote.ToProto()
err = badPrivVal.SignVote(ctx, chainID, g)
require.NoError(t, err, "height %d", height)
err = badPrivVal.SignVote(ctx, chainID, b)
require.NoError(t, err, "height %d", height)
goodVote.Signature, badVote.Signature = g.Signature, b.Signature
wrongSigsCommit = types.NewCommit(goodVote.Height, goodVote.Round,
blockID, []types.CommitSig{goodVote.CommitSig(), badVote.CommitSig()})
}
}
func TestValidateBlockEvidence(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
logger := log.TestingLogger()
proxyApp := proxy.New(abciclient.NewLocalClient(logger, &testApp{}), logger, proxy.NopMetrics())
require.NoError(t, proxyApp.Start(ctx))
state, stateDB, privVals := makeState(t, 4, 1)
stateStore := sm.NewStore(stateDB)
blockStore := store.NewBlockStore(dbm.NewMemDB())
defaultEvidenceTime := time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)
evpool := &mocks.EvidencePool{}
evpool.On("CheckEvidence", ctx, mock.AnythingOfType("types.EvidenceList")).Return(nil)
evpool.On("Update", ctx, mock.AnythingOfType("state.State"), mock.AnythingOfType("types.EvidenceList")).Return()
evpool.On("ABCIEvidence", mock.AnythingOfType("int64"), mock.AnythingOfType("[]types.Evidence")).Return(
[]abci.Evidence{})
eventBus := eventbus.NewDefault(logger)
require.NoError(t, eventBus.Start(ctx))
mp := &mpmocks.Mempool{}
mp.On("Lock").Return()
mp.On("Unlock").Return()
mp.On("FlushAppConn", mock.Anything).Return(nil)
mp.On("Update",
mock.Anything,
mock.Anything,
mock.Anything,
mock.Anything,
mock.Anything,
mock.Anything).Return(nil)
state.ConsensusParams.Evidence.MaxBytes = 1000
blockExec := sm.NewBlockExecutor(
stateStore,
log.TestingLogger(),
proxyApp,
mp,
evpool,
blockStore,
eventBus,
)
lastCommit := types.NewCommit(0, 0, types.BlockID{}, nil)
for height := int64(1); height < validationTestsStopHeight; height++ {
proposerAddr := state.Validators.GetProposer().Address
maxBytesEvidence := state.ConsensusParams.Evidence.MaxBytes
if height > 1 {
/*
A block with too much evidence fails
*/
evidence := make([]types.Evidence, 0)
var currentBytes int64
// more bytes than the maximum allowed for evidence
for currentBytes <= maxBytesEvidence {
newEv, err := types.NewMockDuplicateVoteEvidenceWithValidator(ctx, height, time.Now(),
privVals[proposerAddr.String()], chainID)
require.NoError(t, err)
evidence = append(evidence, newEv)
currentBytes += int64(len(newEv.Bytes()))
}
block := state.MakeBlock(height, testfactory.MakeTenTxs(height), lastCommit, evidence, proposerAddr)
err := blockExec.ValidateBlock(ctx, state, block)
if assert.Error(t, err) {
_, ok := err.(*types.ErrEvidenceOverflow)
require.True(t, ok, "expected error to be of type ErrEvidenceOverflow at height %d but got %v", height, err)
}
}
/*
A good block with several pieces of good evidence passes
*/
evidence := make([]types.Evidence, 0)
var currentBytes int64
// precisely the amount of allowed evidence
for {
newEv, err := types.NewMockDuplicateVoteEvidenceWithValidator(ctx, height, defaultEvidenceTime,
privVals[proposerAddr.String()], chainID)
require.NoError(t, err)
currentBytes += int64(len(newEv.Bytes()))
if currentBytes >= maxBytesEvidence {
break
}
evidence = append(evidence, newEv)
}
state, _, lastCommit = makeAndCommitGoodBlock(
ctx,
t,
state,
height,
lastCommit,
proposerAddr,
blockExec,
privVals,
evidence,
)
}
}