package e2e_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
e2e "github.com/tendermint/tendermint/test/e2e/pkg"
|
|
"github.com/tendermint/tendermint/types"
|
|
)
|
|
|
|
// Tests that validator sets are available and correct according to
|
|
// scheduled validator updates.
|
|
func TestValidator_Sets(t *testing.T) {
|
|
testNode(t, func(ctx context.Context, t *testing.T, node e2e.Node) {
|
|
client, err := node.Client()
|
|
require.NoError(t, err)
|
|
status, err := client.Status(ctx)
|
|
require.NoError(t, err)
|
|
|
|
first := status.SyncInfo.EarliestBlockHeight
|
|
|
|
// for nodes that have to catch up, we should only
|
|
// check the validator sets for nodes after this
|
|
// point, to avoid inconsistencies with backfill.
|
|
if node.StartAt > first {
|
|
first = node.StartAt
|
|
}
|
|
|
|
last := status.SyncInfo.LatestBlockHeight
|
|
|
|
// skip first block if node is pruning blocks, to avoid race conditions
|
|
if node.RetainBlocks > 0 {
|
|
first++
|
|
}
|
|
|
|
valSchedule := newValidatorSchedule(*node.Testnet)
|
|
valSchedule.Increment(first - node.Testnet.InitialHeight)
|
|
|
|
for h := first; h <= last; h++ {
|
|
validators := []*types.Validator{}
|
|
perPage := 100
|
|
for page := 1; ; page++ {
|
|
resp, err := client.Validators(ctx, &(h), &(page), &perPage)
|
|
require.NoError(t, err)
|
|
validators = append(validators, resp.Validators...)
|
|
if len(validators) == resp.Total {
|
|
break
|
|
}
|
|
}
|
|
require.Equal(t, valSchedule.Set.Validators, validators,
|
|
"incorrect validator set at height %v", h)
|
|
valSchedule.Increment(1)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Tests that a validator proposes blocks when it's supposed to. It tolerates some
|
|
// missed blocks, e.g. due to testnet perturbations.
|
|
func TestValidator_Propose(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
blocks := fetchBlockChain(ctx, t)
|
|
testNode(t, func(ctx context.Context, t *testing.T, node e2e.Node) {
|
|
if node.Mode != e2e.ModeValidator {
|
|
return
|
|
}
|
|
address := node.PrivvalKey.PubKey().Address()
|
|
valSchedule := newValidatorSchedule(*node.Testnet)
|
|
|
|
expectCount := 0
|
|
proposeCount := 0
|
|
for _, block := range blocks {
|
|
if bytes.Equal(valSchedule.Set.Proposer.Address, address) {
|
|
expectCount++
|
|
if bytes.Equal(block.ProposerAddress, address) {
|
|
proposeCount++
|
|
}
|
|
}
|
|
valSchedule.Increment(1)
|
|
}
|
|
|
|
require.False(t, proposeCount == 0 && expectCount > 0,
|
|
"node did not propose any blocks (expected %v)", expectCount)
|
|
if expectCount > 5 {
|
|
require.GreaterOrEqual(t, proposeCount, 3, "validator didn't propose even 3 blocks")
|
|
}
|
|
})
|
|
}
|
|
|
|
// Tests that a validator signs blocks when it's supposed to. It tolerates some
|
|
// missed blocks, e.g. due to testnet perturbations.
|
|
func TestValidator_Sign(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
blocks := fetchBlockChain(ctx, t)
|
|
testNode(t, func(ctx context.Context, t *testing.T, node e2e.Node) {
|
|
if node.Mode != e2e.ModeValidator {
|
|
return
|
|
}
|
|
address := node.PrivvalKey.PubKey().Address()
|
|
valSchedule := newValidatorSchedule(*node.Testnet)
|
|
|
|
expectCount := 0
|
|
signCount := 0
|
|
for _, block := range blocks[1:] { // Skip first block, since it has no signatures
|
|
signed := false
|
|
for _, sig := range block.LastCommit.Signatures {
|
|
if bytes.Equal(sig.ValidatorAddress, address) {
|
|
signed = true
|
|
break
|
|
}
|
|
}
|
|
if valSchedule.Set.HasAddress(address) {
|
|
expectCount++
|
|
if signed {
|
|
signCount++
|
|
}
|
|
} else {
|
|
require.False(t, signed, "unexpected signature for block %v", block.LastCommit.Height)
|
|
}
|
|
valSchedule.Increment(1)
|
|
}
|
|
|
|
require.False(t, signCount == 0 && expectCount > 0,
|
|
"validator did not sign any blocks (expected %v)", expectCount)
|
|
if expectCount > 7 {
|
|
require.GreaterOrEqual(t, signCount, 3, "validator didn't sign even 3 blocks (expected %v)", expectCount)
|
|
}
|
|
})
|
|
}
|
|
|
|
// validatorSchedule is a validator set iterator, which takes into account
|
|
// validator set updates.
|
|
type validatorSchedule struct {
|
|
Set *types.ValidatorSet
|
|
height int64
|
|
updates map[int64]map[*e2e.Node]int64
|
|
}
|
|
|
|
func newValidatorSchedule(testnet e2e.Testnet) *validatorSchedule {
|
|
valMap := testnet.Validators // genesis validators
|
|
if v, ok := testnet.ValidatorUpdates[0]; ok { // InitChain validators
|
|
valMap = v
|
|
}
|
|
return &validatorSchedule{
|
|
height: testnet.InitialHeight,
|
|
Set: types.NewValidatorSet(makeVals(valMap)),
|
|
updates: testnet.ValidatorUpdates,
|
|
}
|
|
}
|
|
|
|
func (s *validatorSchedule) Increment(heights int64) {
|
|
for i := int64(0); i < heights; i++ {
|
|
s.height++
|
|
if s.height > 2 {
|
|
// validator set updates are offset by 2, since they only take effect
|
|
// two blocks after they're returned.
|
|
if update, ok := s.updates[s.height-2]; ok {
|
|
if err := s.Set.UpdateWithChangeSet(makeVals(update)); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|
|
s.Set.IncrementProposerPriority(1)
|
|
}
|
|
}
|
|
|
|
func makeVals(valMap map[*e2e.Node]int64) []*types.Validator {
|
|
vals := make([]*types.Validator, 0, len(valMap))
|
|
for node, power := range valMap {
|
|
vals = append(vals, types.NewValidator(node.PrivvalKey.PubKey(), power))
|
|
}
|
|
return vals
|
|
}
|