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.
 
 
 
 
 
 

180 lines
5.1 KiB

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)
require.NoError(t, 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)
require.NoError(t, 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++
}
}
require.NoError(t, 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)
}
require.NoError(t, 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) error {
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 {
return err
}
}
}
s.Set.IncrementProposerPriority(1)
}
return nil
}
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
}