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.
 
 
 
 
 
 

405 lines
12 KiB

package state_test
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/ed25519"
cryptoenc "github.com/tendermint/tendermint/crypto/encoding"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/mempool/mock"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
"github.com/tendermint/tendermint/proxy"
sm "github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types"
tmtime "github.com/tendermint/tendermint/types/time"
)
var (
chainID = "execution_chain"
testPartSize uint32 = 65536
nTxsPerBlock = 10
)
func TestApplyBlock(t *testing.T) {
app := &testApp{}
cc := proxy.NewLocalClientCreator(app)
proxyApp := proxy.NewAppConns(cc)
err := proxyApp.Start()
require.Nil(t, err)
defer proxyApp.Stop() //nolint:errcheck // ignore for tests
state, stateDB, _ := makeState(1, 1)
blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(),
mock.Mempool{}, sm.MockEvidencePool{})
block := makeBlock(state, 1)
blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: block.MakePartSet(testPartSize).Header()}
state, retainHeight, err := blockExec.ApplyBlock(state, blockID, block)
require.Nil(t, err)
assert.EqualValues(t, retainHeight, 1)
// TODO check state and mempool
assert.EqualValues(t, 1, state.Version.Consensus.App, "App version wasn't updated")
}
// TestBeginBlockValidators ensures we send absent validators list.
func TestBeginBlockValidators(t *testing.T) {
app := &testApp{}
cc := proxy.NewLocalClientCreator(app)
proxyApp := proxy.NewAppConns(cc)
err := proxyApp.Start()
require.Nil(t, err)
defer proxyApp.Stop() //nolint:errcheck // no need to check error again
state, stateDB, _ := makeState(2, 2)
prevHash := state.LastBlockID.Hash
prevParts := types.PartSetHeader{}
prevBlockID := types.BlockID{Hash: prevHash, PartSetHeader: prevParts}
var (
now = tmtime.Now()
commitSig0 = types.NewCommitSigForBlock(
[]byte("Signature1"),
state.Validators.Validators[0].Address,
now)
commitSig1 = types.NewCommitSigForBlock(
[]byte("Signature2"),
state.Validators.Validators[1].Address,
now)
absentSig = types.NewCommitSigAbsent()
)
testCases := []struct {
desc string
lastCommitSigs []types.CommitSig
expectedAbsentValidators []int
}{
{"none absent", []types.CommitSig{commitSig0, commitSig1}, []int{}},
{"one absent", []types.CommitSig{commitSig0, absentSig}, []int{1}},
{"multiple absent", []types.CommitSig{absentSig, absentSig}, []int{0, 1}},
}
for _, tc := range testCases {
lastCommit := types.NewCommit(1, 0, prevBlockID, tc.lastCommitSigs)
// block for height 2
block, _ := state.MakeBlock(2, makeTxs(2), lastCommit, nil, state.Validators.GetProposer().Address)
_, err = sm.ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), stateDB)
require.Nil(t, err, tc.desc)
// -> app receives a list of validators with a bool indicating if they signed
ctr := 0
for i, v := range app.CommitVotes {
if ctr < len(tc.expectedAbsentValidators) &&
tc.expectedAbsentValidators[ctr] == i {
assert.False(t, v.SignedLastBlock)
ctr++
} else {
assert.True(t, v.SignedLastBlock)
}
}
}
}
// TestBeginBlockByzantineValidators ensures we send byzantine validators list.
func TestBeginBlockByzantineValidators(t *testing.T) {
app := &testApp{}
cc := proxy.NewLocalClientCreator(app)
proxyApp := proxy.NewAppConns(cc)
err := proxyApp.Start()
require.Nil(t, err)
defer proxyApp.Stop() //nolint:errcheck // ignore for tests
state, stateDB, privVals := makeState(2, 12)
prevHash := state.LastBlockID.Hash
prevParts := types.PartSetHeader{}
prevBlockID := types.BlockID{Hash: prevHash, PartSetHeader: prevParts}
height1, val1 := int64(8), state.Validators.Validators[0].Address
height2, val2 := int64(3), state.Validators.Validators[1].Address
ev1 := types.NewMockDuplicateVoteEvidenceWithValidator(height1, time.Now(), privVals[val1.String()], chainID)
ev2 := types.NewMockDuplicateVoteEvidenceWithValidator(height2, time.Now(), privVals[val2.String()], chainID)
now := tmtime.Now()
valSet := state.Validators
testCases := []struct {
desc string
evidence []types.Evidence
expectedByzantineValidators []abci.Evidence
}{
{"none byzantine", []types.Evidence{}, []abci.Evidence{}},
{"one byzantine", []types.Evidence{ev1}, []abci.Evidence{types.TM2PB.Evidence(ev1, valSet, now)}},
{"multiple byzantine", []types.Evidence{ev1, ev2}, []abci.Evidence{
types.TM2PB.Evidence(ev1, valSet, now),
types.TM2PB.Evidence(ev2, valSet, now)}},
}
var (
commitSig0 = types.NewCommitSigForBlock(
[]byte("Signature1"),
state.Validators.Validators[0].Address,
now)
commitSig1 = types.NewCommitSigForBlock(
[]byte("Signature2"),
state.Validators.Validators[1].Address,
now)
)
commitSigs := []types.CommitSig{commitSig0, commitSig1}
lastCommit := types.NewCommit(9, 0, prevBlockID, commitSigs)
for _, tc := range testCases {
block, _ := state.MakeBlock(10, makeTxs(2), lastCommit, nil, state.Validators.GetProposer().Address)
block.Time = now
block.Evidence.Evidence = tc.evidence
_, err = sm.ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), stateDB)
require.Nil(t, err, tc.desc)
// -> app must receive an index of the byzantine validator
assert.Equal(t, tc.expectedByzantineValidators, app.ByzantineValidators, tc.desc)
}
}
func TestValidateValidatorUpdates(t *testing.T) {
pubkey1 := ed25519.GenPrivKey().PubKey()
pubkey2 := ed25519.GenPrivKey().PubKey()
pk1, err := cryptoenc.PubKeyToProto(pubkey1)
assert.NoError(t, err)
pk2, err := cryptoenc.PubKeyToProto(pubkey2)
assert.NoError(t, err)
defaultValidatorParams := tmproto.ValidatorParams{PubKeyTypes: []string{types.ABCIPubKeyTypeEd25519}}
testCases := []struct {
name string
abciUpdates []abci.ValidatorUpdate
validatorParams tmproto.ValidatorParams
shouldErr bool
}{
{
"adding a validator is OK",
[]abci.ValidatorUpdate{{PubKey: pk2, Power: 20}},
defaultValidatorParams,
false,
},
{
"updating a validator is OK",
[]abci.ValidatorUpdate{{PubKey: pk1, Power: 20}},
defaultValidatorParams,
false,
},
{
"removing a validator is OK",
[]abci.ValidatorUpdate{{PubKey: pk2, Power: 0}},
defaultValidatorParams,
false,
},
{
"adding a validator with negative power results in error",
[]abci.ValidatorUpdate{{PubKey: pk2, Power: -100}},
defaultValidatorParams,
true,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
err := sm.ValidateValidatorUpdates(tc.abciUpdates, tc.validatorParams)
if tc.shouldErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestUpdateValidators(t *testing.T) {
pubkey1 := ed25519.GenPrivKey().PubKey()
val1 := types.NewValidator(pubkey1, 10)
pubkey2 := ed25519.GenPrivKey().PubKey()
val2 := types.NewValidator(pubkey2, 20)
pk, err := cryptoenc.PubKeyToProto(pubkey1)
require.NoError(t, err)
pk2, err := cryptoenc.PubKeyToProto(pubkey2)
require.NoError(t, err)
testCases := []struct {
name string
currentSet *types.ValidatorSet
abciUpdates []abci.ValidatorUpdate
resultingSet *types.ValidatorSet
shouldErr bool
}{
{
"adding a validator is OK",
types.NewValidatorSet([]*types.Validator{val1}),
[]abci.ValidatorUpdate{{PubKey: pk2, Power: 20}},
types.NewValidatorSet([]*types.Validator{val1, val2}),
false,
},
{
"updating a validator is OK",
types.NewValidatorSet([]*types.Validator{val1}),
[]abci.ValidatorUpdate{{PubKey: pk, Power: 20}},
types.NewValidatorSet([]*types.Validator{types.NewValidator(pubkey1, 20)}),
false,
},
{
"removing a validator is OK",
types.NewValidatorSet([]*types.Validator{val1, val2}),
[]abci.ValidatorUpdate{{PubKey: pk2, Power: 0}},
types.NewValidatorSet([]*types.Validator{val1}),
false,
},
{
"removing a non-existing validator results in error",
types.NewValidatorSet([]*types.Validator{val1}),
[]abci.ValidatorUpdate{{PubKey: pk2, Power: 0}},
types.NewValidatorSet([]*types.Validator{val1}),
true,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
updates, err := types.PB2TM.ValidatorUpdates(tc.abciUpdates)
assert.NoError(t, err)
err = tc.currentSet.UpdateWithChangeSet(updates)
if tc.shouldErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
require.Equal(t, tc.resultingSet.Size(), tc.currentSet.Size())
assert.Equal(t, tc.resultingSet.TotalVotingPower(), tc.currentSet.TotalVotingPower())
assert.Equal(t, tc.resultingSet.Validators[0].Address, tc.currentSet.Validators[0].Address)
if tc.resultingSet.Size() > 1 {
assert.Equal(t, tc.resultingSet.Validators[1].Address, tc.currentSet.Validators[1].Address)
}
}
})
}
}
// TestEndBlockValidatorUpdates ensures we update validator set and send an event.
func TestEndBlockValidatorUpdates(t *testing.T) {
app := &testApp{}
cc := proxy.NewLocalClientCreator(app)
proxyApp := proxy.NewAppConns(cc)
err := proxyApp.Start()
require.Nil(t, err)
defer proxyApp.Stop() //nolint:errcheck // ignore for tests
state, stateDB, _ := makeState(1, 1)
blockExec := sm.NewBlockExecutor(
stateDB,
log.TestingLogger(),
proxyApp.Consensus(),
mock.Mempool{},
sm.MockEvidencePool{},
)
eventBus := types.NewEventBus()
err = eventBus.Start()
require.NoError(t, err)
defer eventBus.Stop() //nolint:errcheck // ignore for tests
blockExec.SetEventBus(eventBus)
updatesSub, err := eventBus.Subscribe(
context.Background(),
"TestEndBlockValidatorUpdates",
types.EventQueryValidatorSetUpdates,
)
require.NoError(t, err)
block := makeBlock(state, 1)
blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: block.MakePartSet(testPartSize).Header()}
pubkey := ed25519.GenPrivKey().PubKey()
pk, err := cryptoenc.PubKeyToProto(pubkey)
require.NoError(t, err)
app.ValidatorUpdates = []abci.ValidatorUpdate{
{PubKey: pk, Power: 10},
}
state, _, err = blockExec.ApplyBlock(state, blockID, block)
require.Nil(t, err)
// test new validator was added to NextValidators
if assert.Equal(t, state.Validators.Size()+1, state.NextValidators.Size()) {
idx, _ := state.NextValidators.GetByAddress(pubkey.Address())
if idx < 0 {
t.Fatalf("can't find address %v in the set %v", pubkey.Address(), state.NextValidators)
}
}
// test we threw an event
select {
case msg := <-updatesSub.Out():
event, ok := msg.Data().(types.EventDataValidatorSetUpdates)
require.True(t, ok, "Expected event of type EventDataValidatorSetUpdates, got %T", msg.Data())
if assert.NotEmpty(t, event.ValidatorUpdates) {
assert.Equal(t, pubkey, event.ValidatorUpdates[0].PubKey)
assert.EqualValues(t, 10, event.ValidatorUpdates[0].VotingPower)
}
case <-updatesSub.Cancelled():
t.Fatalf("updatesSub was cancelled (reason: %v)", updatesSub.Err())
case <-time.After(1 * time.Second):
t.Fatal("Did not receive EventValidatorSetUpdates within 1 sec.")
}
}
// TestEndBlockValidatorUpdatesResultingInEmptySet checks that processing validator updates that
// would result in empty set causes no panic, an error is raised and NextValidators is not updated
func TestEndBlockValidatorUpdatesResultingInEmptySet(t *testing.T) {
app := &testApp{}
cc := proxy.NewLocalClientCreator(app)
proxyApp := proxy.NewAppConns(cc)
err := proxyApp.Start()
require.Nil(t, err)
defer proxyApp.Stop() //nolint:errcheck // ignore for tests
state, stateDB, _ := makeState(1, 1)
blockExec := sm.NewBlockExecutor(
stateDB,
log.TestingLogger(),
proxyApp.Consensus(),
mock.Mempool{},
sm.MockEvidencePool{},
)
block := makeBlock(state, 1)
blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: block.MakePartSet(testPartSize).Header()}
vp, err := cryptoenc.PubKeyToProto(state.Validators.Validators[0].PubKey)
require.NoError(t, err)
// Remove the only validator
app.ValidatorUpdates = []abci.ValidatorUpdate{
{PubKey: vp, Power: 0},
}
assert.NotPanics(t, func() { state, _, err = blockExec.ApplyBlock(state, blockID, block) })
assert.NotNil(t, err)
assert.NotEmpty(t, state.NextValidators.Validators)
}