validators_test.gno
6.50 Kb · 242 lines
1package validators
2
3import (
4 "chain/runtime"
5 "math"
6 "strings"
7 "testing"
8
9 "gno.land/p/nt/bptree/v0"
10 "gno.land/p/nt/poa/v0"
11 "gno.land/p/nt/testutils/v0"
12 "gno.land/p/nt/uassert/v0"
13 "gno.land/p/nt/ufmt/v0"
14 "gno.land/p/sys/validators"
15)
16
17// cur is a zero-value realm used as a placeholder when forwarding to
18// uassert/urequire dispatch helpers that gained an `rlm realm` param.
19// These tests pass `func()` callbacks (no crossing inside the callback),
20// so rlm is ignored — a nil realm here is safe.
21var cur realm
22
23// generateTestValidators generates a dummy validator set
24func generateTestValidators(count int) []validators.Validator {
25 vals := make([]validators.Validator, 0, count)
26
27 for i := 0; i < count; i++ {
28 val := validators.Validator{
29 Address: testutils.TestAddress(ufmt.Sprintf("%d", i)),
30 PubKey: "public-key",
31 VotingPower: 10,
32 }
33
34 vals = append(vals, val)
35 }
36
37 return vals
38}
39
40func TestValidators_AddRemove(t *testing.T) {
41 // Clear any changes
42 changes = bptree.NewBPTree32()
43
44 var (
45 vals = generateTestValidators(100)
46 initialHeight = int64(123)
47 )
48
49 // Add in the validators
50 for _, val := range vals {
51 addValidator(val)
52
53 // Make sure the validator is added
54 uassert.True(t, vp.IsValidator(val.Address))
55
56 testing.SkipHeights(1)
57 }
58
59 for i := initialHeight; i < initialHeight+int64(len(vals)); i++ {
60 // Make sure the changes are saved
61 chs := GetChanges(i, initialHeight+int64(len(vals)))
62
63 // We use the funky index calculation to make sure
64 // changes are properly handled for each block span
65 uassert.Equal(t, initialHeight+int64(len(vals))-i, int64(len(chs)))
66
67 for index, val := range vals[i-initialHeight:] {
68 // Make sure the changes are equal to the additions
69 ch := chs[index]
70
71 uassert.Equal(t, val.Address, ch.Address)
72 uassert.Equal(t, val.PubKey, ch.PubKey)
73 uassert.Equal(t, val.VotingPower, ch.VotingPower)
74 }
75 }
76
77 // Save the beginning height for the removal
78 initialRemoveHeight := runtime.ChainHeight()
79
80 // Clear any changes
81 changes = bptree.NewBPTree32()
82
83 // Remove the validators
84 for _, val := range vals {
85 removeValidator(val.Address)
86
87 // Make sure the validator is removed
88 uassert.False(t, vp.IsValidator(val.Address))
89
90 testing.SkipHeights(1)
91 }
92
93 for i := initialRemoveHeight; i < initialRemoveHeight+int64(len(vals)); i++ {
94 // Make sure the changes are saved
95 chs := GetChanges(i, initialRemoveHeight+int64(len(vals)))
96
97 // We use the funky index calculation to make sure
98 // changes are properly handled for each block span
99 uassert.Equal(t, initialRemoveHeight+int64(len(vals))-i, int64(len(chs)))
100
101 for index, val := range vals[i-initialRemoveHeight:] {
102 // Make sure the changes are equal to the additions
103 ch := chs[index]
104
105 uassert.Equal(t, val.Address, ch.Address)
106 uassert.Equal(t, val.PubKey, ch.PubKey)
107 uassert.Equal(t, uint64(0), ch.VotingPower)
108 }
109 }
110}
111
112// TestGetChanges_BoundedRange verifies that GetChanges(from, to) correctly
113// returns only changes within the [from, to] block range.
114func TestGetChanges_BoundedRange(t *testing.T) {
115 changes = bptree.NewBPTree32()
116 vp = poa.NewPoA()
117
118 vals := generateTestValidators(3)
119
120 // Store additions at block h1
121 h1 := runtime.ChainHeight()
122 for _, val := range vals {
123 addValidator(val)
124 }
125 testing.SkipHeights(1)
126
127 // Store removals at block h2
128 h2 := runtime.ChainHeight()
129 for _, val := range vals {
130 removeValidator(val.Address)
131 }
132 testing.SkipHeights(1)
133
134 // Query spanning both blocks returns all changes
135 all := GetChanges(h1, h2)
136 uassert.Equal(t, 6, len(all))
137
138 // Query for h1 only returns additions
139 atH1 := GetChanges(h1, h1)
140 uassert.Equal(t, 3, len(atH1))
141 for i, ch := range atH1 {
142 uassert.Equal(t, vals[i].Address, ch.Address)
143 uassert.True(t, ch.VotingPower > 0)
144 }
145
146 // Query for h2 only returns removals
147 atH2 := GetChanges(h2, h2)
148 uassert.Equal(t, 3, len(atH2))
149 for i, ch := range atH2 {
150 uassert.Equal(t, vals[i].Address, ch.Address)
151 uassert.Equal(t, uint64(0), ch.VotingPower)
152 }
153
154 // Query beyond stored range returns empty
155 uassert.Equal(t, 0, len(GetChanges(h2+1, h2+1)))
156}
157
158// TestRender_ShowsNewestWhenOverLimit verifies that Render displays the newest
159// maxDisplay buckets when there are more change buckets than maxDisplay.
160func TestRender_ShowsNewestWhenOverLimit(t *testing.T) {
161 changes = bptree.NewBPTree32()
162 vp = poa.NewPoA()
163
164 const total = 13
165 const maxDisplay = 10
166
167 vals := generateTestValidators(total)
168 base := runtime.ChainHeight()
169
170 for i := 0; i < total; i++ {
171 h := base + int64(i)
172 changes.Set(getBlockID(h), []change{
173 {blockNum: h, validator: vals[i]},
174 })
175 }
176
177 output := Render("")
178
179 // Newest maxDisplay buckets must appear.
180 for i := total - maxDisplay; i < total; i++ {
181 h := base + int64(i)
182 uassert.True(t, strings.Contains(output, ufmt.Sprintf("#%d:", h)),
183 ufmt.Sprintf("expected block #%d in output", h))
184 }
185
186 // Oldest (total - maxDisplay) buckets must NOT appear.
187 for i := 0; i < total-maxDisplay; i++ {
188 h := base + int64(i)
189 uassert.False(t, strings.Contains(output, ufmt.Sprintf("#%d:", h)),
190 ufmt.Sprintf("block #%d should be absent from output", h))
191 }
192}
193
194// TestRender_ShowsAllWhenUnderLimit verifies that all buckets are displayed when
195// there are fewer than maxDisplay, and that no panic occurs from a negative offset.
196func TestRender_ShowsAllWhenUnderLimit(t *testing.T) {
197 changes = bptree.NewBPTree32()
198 vp = poa.NewPoA()
199
200 const total = 5 // less than maxDisplay=10
201
202 vals := generateTestValidators(total)
203 base := runtime.ChainHeight()
204
205 for i := 0; i < total; i++ {
206 h := base + int64(i)
207 changes.Set(getBlockID(h), []change{
208 {blockNum: h, validator: vals[i]},
209 })
210 }
211
212 output := Render("")
213
214 for i := 0; i < total; i++ {
215 h := base + int64(i)
216 uassert.True(t, strings.Contains(output, ufmt.Sprintf("#%d:", h)),
217 ufmt.Sprintf("expected block #%d in output", h))
218 }
219}
220
221func TestGetChanges_PanicsOnInvalidRange(cur realm, t *testing.T) {
222 uassert.PanicsWithMessage(t, cur, "invalid range: from must be <= to", func() {
223 GetChanges(10, 5)
224 })
225}
226
227func TestGetChanges_ClampsMaxInt64(t *testing.T) {
228 changes = bptree.NewBPTree32()
229
230 vals := generateTestValidators(1)
231
232 // Simulate a validator change at block math.MaxInt64-1 (the boundary value).
233 changes.Set(getBlockID(math.MaxInt64-1), []change{
234 {blockNum: math.MaxInt64 - 1, validator: vals[0]},
235 })
236
237 // Passing math.MaxInt64 as "to" means "get all updates from here onwards".
238 // The clamp (to = MaxInt64-1) must still include the boundary block.
239 result := GetChanges(math.MaxInt64-1, math.MaxInt64)
240 uassert.Equal(t, 1, len(result))
241 uassert.Equal(t, vals[0].Address, result[0].Address)
242}