validators_test.gno
4.44 Kb · 172 lines
1package validators
2
3import (
4 "chain/runtime"
5 "math"
6 "testing"
7
8 "gno.land/p/nt/avl/v0"
9 "gno.land/p/nt/poa/v0"
10 "gno.land/p/nt/testutils/v0"
11 "gno.land/p/nt/uassert/v0"
12 "gno.land/p/nt/ufmt/v0"
13 "gno.land/p/sys/validators"
14)
15
16// generateTestValidators generates a dummy validator set
17func generateTestValidators(count int) []validators.Validator {
18 vals := make([]validators.Validator, 0, count)
19
20 for i := 0; i < count; i++ {
21 val := validators.Validator{
22 Address: testutils.TestAddress(ufmt.Sprintf("%d", i)),
23 PubKey: "public-key",
24 VotingPower: 10,
25 }
26
27 vals = append(vals, val)
28 }
29
30 return vals
31}
32
33func TestValidators_AddRemove(t *testing.T) {
34 // Clear any changes
35 changes = avl.NewTree()
36
37 var (
38 vals = generateTestValidators(100)
39 initialHeight = int64(123)
40 )
41
42 // Add in the validators
43 for _, val := range vals {
44 addValidator(val)
45
46 // Make sure the validator is added
47 uassert.True(t, vp.IsValidator(val.Address))
48
49 testing.SkipHeights(1)
50 }
51
52 for i := initialHeight; i < initialHeight+int64(len(vals)); i++ {
53 // Make sure the changes are saved
54 chs := GetChanges(i, initialHeight+int64(len(vals)))
55
56 // We use the funky index calculation to make sure
57 // changes are properly handled for each block span
58 uassert.Equal(t, initialHeight+int64(len(vals))-i, int64(len(chs)))
59
60 for index, val := range vals[i-initialHeight:] {
61 // Make sure the changes are equal to the additions
62 ch := chs[index]
63
64 uassert.Equal(t, val.Address, ch.Address)
65 uassert.Equal(t, val.PubKey, ch.PubKey)
66 uassert.Equal(t, val.VotingPower, ch.VotingPower)
67 }
68 }
69
70 // Save the beginning height for the removal
71 initialRemoveHeight := runtime.ChainHeight()
72
73 // Clear any changes
74 changes = avl.NewTree()
75
76 // Remove the validators
77 for _, val := range vals {
78 removeValidator(val.Address)
79
80 // Make sure the validator is removed
81 uassert.False(t, vp.IsValidator(val.Address))
82
83 testing.SkipHeights(1)
84 }
85
86 for i := initialRemoveHeight; i < initialRemoveHeight+int64(len(vals)); i++ {
87 // Make sure the changes are saved
88 chs := GetChanges(i, initialRemoveHeight+int64(len(vals)))
89
90 // We use the funky index calculation to make sure
91 // changes are properly handled for each block span
92 uassert.Equal(t, initialRemoveHeight+int64(len(vals))-i, int64(len(chs)))
93
94 for index, val := range vals[i-initialRemoveHeight:] {
95 // Make sure the changes are equal to the additions
96 ch := chs[index]
97
98 uassert.Equal(t, val.Address, ch.Address)
99 uassert.Equal(t, val.PubKey, ch.PubKey)
100 uassert.Equal(t, uint64(0), ch.VotingPower)
101 }
102 }
103}
104
105// TestGetChanges_BoundedRange verifies that GetChanges(from, to) correctly
106// returns only changes within the [from, to] block range.
107func TestGetChanges_BoundedRange(t *testing.T) {
108 changes = avl.NewTree()
109 vp = poa.NewPoA()
110
111 vals := generateTestValidators(3)
112
113 // Store additions at block h1
114 h1 := runtime.ChainHeight()
115 for _, val := range vals {
116 addValidator(val)
117 }
118 testing.SkipHeights(1)
119
120 // Store removals at block h2
121 h2 := runtime.ChainHeight()
122 for _, val := range vals {
123 removeValidator(val.Address)
124 }
125 testing.SkipHeights(1)
126
127 // Query spanning both blocks returns all changes
128 all := GetChanges(h1, h2)
129 uassert.Equal(t, 6, len(all))
130
131 // Query for h1 only returns additions
132 atH1 := GetChanges(h1, h1)
133 uassert.Equal(t, 3, len(atH1))
134 for i, ch := range atH1 {
135 uassert.Equal(t, vals[i].Address, ch.Address)
136 uassert.True(t, ch.VotingPower > 0)
137 }
138
139 // Query for h2 only returns removals
140 atH2 := GetChanges(h2, h2)
141 uassert.Equal(t, 3, len(atH2))
142 for i, ch := range atH2 {
143 uassert.Equal(t, vals[i].Address, ch.Address)
144 uassert.Equal(t, uint64(0), ch.VotingPower)
145 }
146
147 // Query beyond stored range returns empty
148 uassert.Equal(t, 0, len(GetChanges(h2+1, h2+1)))
149}
150
151func TestGetChanges_PanicsOnInvalidRange(t *testing.T) {
152 uassert.PanicsWithMessage(t, "invalid range: from must be <= to", func() {
153 GetChanges(10, 5)
154 })
155}
156
157func TestGetChanges_ClampsMaxInt64(t *testing.T) {
158 changes = avl.NewTree()
159
160 vals := generateTestValidators(1)
161
162 // Simulate a validator change at block math.MaxInt64-1 (the boundary value).
163 changes.Set(getBlockID(math.MaxInt64-1), []change{
164 {blockNum: math.MaxInt64 - 1, validator: vals[0]},
165 })
166
167 // Passing math.MaxInt64 as "to" means "get all updates from here onwards".
168 // The clamp (to = MaxInt64-1) must still include the boundary block.
169 result := GetChanges(math.MaxInt64-1, math.MaxInt64)
170 uassert.Equal(t, 1, len(result))
171 uassert.Equal(t, vals[0].Address, result[0].Address)
172}