Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}