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