github.com/line/ostracon@v1.0.10-0.20230328032236-7f20145f065d/test/e2e/tests/validator_test.go (about)

     1  package e2e_test
     2  
     3  import (
     4  	"bytes"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/require"
     8  
     9  	"github.com/line/ostracon/crypto/vrf"
    10  	e2e "github.com/line/ostracon/test/e2e/pkg"
    11  	"github.com/line/ostracon/types"
    12  )
    13  
    14  // Tests that validator sets are available and correct according to
    15  // scheduled validator updates.
    16  func TestValidator_Sets(t *testing.T) {
    17  	testNode(t, func(t *testing.T, node e2e.Node) {
    18  		if node.Mode == e2e.ModeSeed {
    19  			return
    20  		}
    21  
    22  		client, err := node.Client()
    23  		require.NoError(t, err)
    24  		status, err := client.Status(ctx)
    25  		require.NoError(t, err)
    26  
    27  		first := status.SyncInfo.EarliestBlockHeight
    28  		last := status.SyncInfo.LatestBlockHeight
    29  
    30  		// skip first block if node is pruning blocks, to avoid race conditions
    31  		if node.RetainBlocks > 0 {
    32  			first++
    33  		}
    34  
    35  		valSchedule := newValidatorSchedule(*node.Testnet)
    36  		valSchedule.Increment(first - node.Testnet.InitialHeight)
    37  
    38  		for h := first; h <= last; h++ {
    39  			validators := []*types.Validator{}
    40  			perPage := 100
    41  			for page := 1; ; page++ {
    42  				resp, err := client.Validators(ctx, &(h), &(page), &perPage)
    43  				require.NoError(t, err)
    44  				validators = append(validators, resp.Validators...)
    45  				if len(validators) == resp.Total {
    46  					break
    47  				}
    48  			}
    49  
    50  			for i := 0; i < len(validators); i++ {
    51  				expected := valSchedule.Set.Validators[i]
    52  				actual := validators[i]
    53  				require.Equal(t, expected.Address, actual.Address,
    54  					"incorrect Address of validator set at height %v", h)
    55  				require.Equal(t, expected.PubKey, actual.PubKey,
    56  					"incorrect PubKey of validator set at height %v", h)
    57  				require.Equal(t, expected.VotingPower, actual.VotingPower,
    58  					"incorrect VotingPower of validator set at height %v", h)
    59  				if expected.ProposerPriority == 0 {
    60  					// ProposerPriority is not changed
    61  					// the other value can ignore since we don't use it
    62  					require.Equal(t, expected.ProposerPriority, actual.ProposerPriority,
    63  						"incorrect ProposerPriority of validator set at height %v", h)
    64  				}
    65  			}
    66  			valSchedule.Increment(1)
    67  		}
    68  	})
    69  }
    70  
    71  // Tests that a validator proposes blocks when it's supposed to. It tolerates some
    72  // missed blocks, e.g. due to testnet perturbations.
    73  func TestValidator_Propose(t *testing.T) {
    74  	t.Skip("Ostracon doesn't select a Proposer based on ProposerPriority")
    75  	blocks := fetchBlockChain(t)
    76  	testNode(t, func(t *testing.T, node e2e.Node) {
    77  		if node.Mode != e2e.ModeValidator {
    78  			return
    79  		}
    80  		address := node.PrivvalKey.PubKey().Address()
    81  		valSchedule := newValidatorSchedule(*node.Testnet)
    82  
    83  		expectCount := 0
    84  		proposeCount := 0
    85  		for _, block := range blocks {
    86  			proofHash, _ := vrf.ProofToHash(block.Proof.Bytes())
    87  			proposer := valSchedule.Set.SelectProposer(proofHash, block.Height, block.Round)
    88  			if bytes.Equal(proposer.Address, address) {
    89  				expectCount++
    90  				if bytes.Equal(block.ProposerAddress, address) {
    91  					proposeCount++
    92  				}
    93  			}
    94  			valSchedule.Increment(1)
    95  		}
    96  
    97  		require.False(t, proposeCount == 0 && expectCount > 0,
    98  			"node did not propose any blocks (expected %v)", expectCount)
    99  		if expectCount > 5 {
   100  			// We changed round-robin selection of proposer
   101  			// High probability of not being a proposer within 5 blocks
   102  			require.GreaterOrEqual(t, proposeCount, 3, "validator didn't propose even 3 blocks")
   103  		}
   104  	})
   105  }
   106  
   107  // Tests that a validator signs blocks when it's supposed to. It tolerates some
   108  // missed blocks, e.g. due to testnet perturbations.
   109  func TestValidator_Sign(t *testing.T) {
   110  	blocks := fetchBlockChain(t)
   111  	testNode(t, func(t *testing.T, node e2e.Node) {
   112  		if node.Mode != e2e.ModeValidator {
   113  			return
   114  		}
   115  		address := node.PrivvalKey.PubKey().Address()
   116  		valSchedule := newValidatorSchedule(*node.Testnet)
   117  
   118  		expectCount := 0
   119  		signCount := 0
   120  		for _, block := range blocks[1:] { // Skip first block, since it has no signatures
   121  			signed := false
   122  			for _, sig := range block.LastCommit.Signatures {
   123  				if bytes.Equal(sig.ValidatorAddress, address) {
   124  					signed = true
   125  					break
   126  				}
   127  			}
   128  			if valSchedule.Set.HasAddress(address) {
   129  				expectCount++
   130  				if signed {
   131  					signCount++
   132  				}
   133  			} else {
   134  				require.False(t, signed, "unexpected signature for block %v", block.LastCommit.Height)
   135  			}
   136  			valSchedule.Increment(1)
   137  		}
   138  
   139  		require.False(t, signCount == 0 && expectCount > 0,
   140  			"validator did not sign any blocks (expected %v)", expectCount)
   141  		if expectCount > 7 {
   142  			require.GreaterOrEqual(t, signCount, 3, "validator didn't sign even 3 blocks (expected %v)", expectCount)
   143  		}
   144  	})
   145  }
   146  
   147  // validatorSchedule is a validator set iterator, which takes into account
   148  // validator set updates.
   149  type validatorSchedule struct {
   150  	Set     *types.ValidatorSet
   151  	height  int64
   152  	updates map[int64]map[*e2e.Node]int64
   153  }
   154  
   155  func newValidatorSchedule(testnet e2e.Testnet) *validatorSchedule {
   156  	valMap := testnet.Validators                  // genesis validators
   157  	if v, ok := testnet.ValidatorUpdates[0]; ok { // InitChain validators
   158  		valMap = v
   159  	}
   160  	return &validatorSchedule{
   161  		height:  testnet.InitialHeight,
   162  		Set:     types.NewValidatorSet(makeVals(valMap)),
   163  		updates: testnet.ValidatorUpdates,
   164  	}
   165  }
   166  
   167  func (s *validatorSchedule) Increment(heights int64) {
   168  	for i := int64(0); i < heights; i++ {
   169  		s.height++
   170  		if s.height > 2 {
   171  			// validator set updates are offset by 2, since they only take effect
   172  			// two blocks after they're returned.
   173  			if update, ok := s.updates[s.height-2]; ok {
   174  				if err := s.Set.UpdateWithChangeSet(makeVals(update)); err != nil {
   175  					panic(err)
   176  				}
   177  			}
   178  		}
   179  		s.Set.IncrementProposerPriority(1)
   180  	}
   181  }
   182  
   183  func makeVals(valMap map[*e2e.Node]int64) []*types.Validator {
   184  	vals := make([]*types.Validator, 0, len(valMap))
   185  	for node, power := range valMap {
   186  		vals = append(vals, types.NewValidator(node.PrivvalKey.PubKey(), power))
   187  	}
   188  	return vals
   189  }