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 }