Kind Realm (renderable)
Name v0
Namespace sys / validators
Exported functions 9
Module gno.land/r/sys/validators/v0
gno 0.9 cache_test.gno gno
⧉
1 package validators 2 3 import ( 4 "testing" 5 6 "gno.land/p/nt/bptree/v0" 7 "gno.land/p/nt/testutils/v0" 8 AssertGenesisValopersConsistent (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})
GetValidator (addr string) struct{Address .uverse.address; PubKey string; VotingPower uint64}
GetValidators () []gno.land/p/sys/validators/v0.Validator
IsValidator (addr string) bool
NewValidatorProposalRequest (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, changes []gno.land/r/sys/validators/v0.ValoperChange, title string, description string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
NewValoperChange (operatorAddress string, power uint64) struct{OperatorAddress .uverse.address; Power uint64}
NotifyValoperChanged (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, op string, signingPubKey string, signingAddress string, keepRunning bool)
Render (string) string
RotateValoperSigningKey (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, op string, oldPubKey string, newPubKey string)
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.
"gno.land/p/nt/uassert/v0"
9 "gno.land/p/nt/urequire/v0"
10 sysparams "gno.land/r/sys/params"
11 )
12
13 // cur is a zero-value realm used as a placeholder when forwarding to
14 // uassert/urequire dispatch helpers that gained an `rlm realm` param.
15 // These tests pass `func()` callbacks (no crossing inside the callback),
16 // so rlm is ignored — a nil realm here is safe.
17 var cur realm
18
19 // resetCache clears valoperCache so subtests don't leak state.
20 func resetCache() {
21 valoperCache = bptree.NewBPTree32()
22 }
23
24 func TestNotifyValoperChanged_HappyPath(cur realm, t *testing.T) {
25 resetCache()
26
27 op := testutils.TestAddress( "op-A" )
28 signingAddr := mustAddr(t, pubKeyA)
29
30 // Caller realm = valopers; auth check passes.
31 testing.SetRealm(testing.NewCodeRealm(valopersRealmPath))
32
33 NotifyValoperChanged(cross(cur), op, pubKeyA, signingAddr, true )
34
35 rawEntry := valoperCache.Get(op.String())
36 urequire.NotNil(t, rawEntry, "cache entry must exist" )
37 entry := rawEntry.(cacheEntry)
38 uassert.Equal(t, pubKeyA, entry.SigningPubKey)
39 uassert.Equal(t, signingAddr, entry.SigningAddress)
40 uassert.Equal(t, true , entry.KeepRunning)
41
42 // Subsequent call updates the same slot.
43 signingAddrB := mustAddr(t, pubKeyB)
44 NotifyValoperChanged(cross(cur), op, pubKeyB, signingAddrB, false )
45
46 rawEntry = valoperCache.Get(op.String())
47 urequire.NotNil(t, rawEntry)
48 entry = rawEntry.(cacheEntry)
49 uassert.Equal(t, pubKeyB, entry.SigningPubKey)
50 uassert.Equal(t, signingAddrB, entry.SigningAddress)
51 uassert.Equal(t, false , entry.KeepRunning)
52 }
53
54 func TestNotifyValoperChanged_RejectsNonValopersCaller(cur realm, t *testing.T) {
55 resetCache()
56
57 op := testutils.TestAddress( "op-A" )
58 signingAddr := mustAddr(t, pubKeyA)
59
60 // Caller realm = some other realm; auth check rejects.
61 testing.SetRealm(testing.NewCodeRealm( "gno.land/r/attacker" ))
62
63 uassert.AbortsContains(t, cur, "caller realm must be " +valopersRealmPath, func () {
64 NotifyValoperChanged(cross(cur), op, pubKeyA, signingAddr, true )
65 })
66
67 // User MsgCall — UserRealm has empty pkgpath; rejected the same way.
68 testing.SetRealm(testing.NewUserRealm(testutils.TestAddress( "user" )))
69
70 uassert.AbortsContains(t, cur, "caller realm must be " +valopersRealmPath, func () {
71 NotifyValoperChanged(cross(cur), op, pubKeyA, signingAddr, true )
72 })
73 }
74
75 func TestRotateValoperSigningKey_AppliesRotation(cur realm, t *testing.T) {
76 resetValset(t)
77 resetCache()
78
79 // Seed the valset with op-A signing under pubKeyA at power=10.
80 testing.SetSysParamStrings(module, submodule, currKey, [] string {pubKeyA + ":10" })
81
82 op := testutils.TestAddress( "op-A" )
83 testing.SetRealm(testing.NewCodeRealm(valopersRealmPath))
84
85 RotateValoperSigningKey(cross(cur), op, pubKeyA, pubKeyB)
86
87 // After rotation the effective set (proposed-when-dirty) should
88 // contain pubKeyB at the same power; pubKeyA should be gone.
89 uassert.True(t, sysparams.ValsetDirty(), "dirty bit set after RotateValoperSigningKey" )
90
91 effective := sysparams.GetValsetEffective()
92 urequire.Equal(t, 1 , len(effective))
93 uassert.Equal(t, pubKeyB, effective[ 0 ].PubKey)
94 uassert.Equal(t, uint64 ( 10 ), effective[ 0 ].VotingPower)
95 }
96
97 func TestRotateValoperSigningKey_OperatorNotInValset_NoOp(cur realm, t *testing.T) {
98 resetValset(t)
99 resetCache()
100
101 // Empty valset; the operator is not currently signing.
102 op := testutils.TestAddress( "op-A" )
103 testing.SetRealm(testing.NewCodeRealm(valopersRealmPath))
104
105 // Should not panic; no sysparams write expected (dirty stays false).
106 uassert.NotAborts(t, cur, func () {
107 RotateValoperSigningKey(cross(cur), op, pubKeyA, pubKeyB)
108 })
109
110 uassert.False(t, sysparams.ValsetDirty(), "dirty bit must remain false on no-op rotation" )
111 uassert.Equal(t, 0 , len(sysparams.GetValsetEffective()))
112 }
113
114 func TestRotateValoperSigningKey_RejectsNonValopersCaller(cur realm, t *testing.T) {
115 resetValset(t)
116 resetCache()
117 testing.SetSysParamStrings(module, submodule, currKey, [] string {pubKeyA + ":10" })
118
119 op := testutils.TestAddress( "op-A" )
120
121 // Foreign realm — rejected.
122 testing.SetRealm(testing.NewCodeRealm( "gno.land/r/attacker" ))
123 uassert.AbortsContains(t, cur, "caller realm must be " +valopersRealmPath, func () {
124 RotateValoperSigningKey(cross(cur), op, pubKeyA, pubKeyB)
125 })
126
127 // User MsgCall — UserRealm pkgpath is "" — rejected.
128 testing.SetRealm(testing.NewUserRealm(testutils.TestAddress( "user" )))
129 uassert.AbortsContains(t, cur, "caller realm must be " +valopersRealmPath, func () {
130 RotateValoperSigningKey(cross(cur), op, pubKeyA, pubKeyB)
131 })
132
133 // Sanity: the valset wasn't mutated by the rejected calls (dirty
134 // bit stays false; effective set still equals the seeded current).
135 uassert.False(t, sysparams.ValsetDirty())
136 effective := sysparams.GetValsetEffective()
137 urequire.Equal(t, 1 , len(effective))
138 uassert.Equal(t, pubKeyA, effective[ 0 ].PubKey)
139 }
140
141 func TestAssertGenesisValopersConsistent_HappyPath(cur realm, t *testing.T) {
142 resetValset(t)
143 resetCache()
144 testing.SetHeight( 0 ) // assertion is genesis-mode only
145
146 // Seed valset:current with two genesis validators.
147 testing.SetSysParamStrings(module, submodule, currKey, [] string {
148 pubKeyA + ":10" ,
149 pubKeyB + ":5" ,
150 })
151
152 // Seed valoperCache with profiles whose SigningAddress matches.
153 opA := testutils.TestAddress( "op-A" )
154 opB := testutils.TestAddress( "op-B" )
155 seedCache(t, [] struct {
156 op address
157 pubKey string
158 keepRunning bool
159 }{
160 {op: opA, pubKey: pubKeyA, keepRunning: true },
161 {op: opB, pubKey: pubKeyB, keepRunning: true },
162 })
163
164 uassert.NotAborts(t, cur, func () {
165 AssertGenesisValopersConsistent(cross(cur))
166 })
167 }
168
169 func TestAssertGenesisValopersConsistent_PanicsOnMissingProfile(cur realm, t *testing.T) {
170 resetValset(t)
171 resetCache()
172 testing.SetHeight( 0 )
173
174 // Two validators in valset:current but only one has a profile.
175 testing.SetSysParamStrings(module, submodule, currKey, [] string {
176 pubKeyA + ":10" ,
177 pubKeyB + ":5" ,
178 })
179
180 opA := testutils.TestAddress( "op-A" )
181 seedCache(t, [] struct {
182 op address
183 pubKey string
184 keepRunning bool
185 }{
186 {op: opA, pubKey: pubKeyA, keepRunning: true },
187 // op for pubKeyB intentionally missing.
188 })
189
190 uassert.AbortsContains(t, cur, "no corresponding valoper profile" , func () {
191 AssertGenesisValopersConsistent(cross(cur))
192 })
193 }
194
195 func TestAssertGenesisValopersConsistent_EmptyValsetTrivial(cur realm, t *testing.T) {
196 resetValset(t)
197 resetCache()
198 testing.SetHeight( 0 )
199
200 // Empty valset → assertion trivially holds (no entries to check).
201 uassert.NotAborts(t, cur, func () {
202 AssertGenesisValopersConsistent(cross(cur))
203 })
204 }
205
206 func TestAssertGenesisValopersConsistent_RejectsPostGenesis(cur realm, t *testing.T) {
207 resetValset(t)
208 resetCache()
209 // Default test height is non-zero (123); the assertion must
210 // refuse to run outside genesis-mode replay.
211 uassert.AbortsContains(t, cur, "only callable during genesis-mode replay" , func () {
212 AssertGenesisValopersConsistent(cross(cur))
213 })
214 }
215
216 func TestRotateValoperSigningKey_AccumulatesAcrossSameBlock(cur realm, t *testing.T) {
217 resetValset(t)
218 resetCache()
219
220 // Seed valset with two operators signing under pubKeyA and pubKeyC.
221 testing.SetSysParamStrings(module, submodule, currKey, [] string {
222 pubKeyA + ":10" ,
223 pubKeyC + ":5" ,
224 })
225
226 opA := testutils.TestAddress( "op-A" )
227 opC := testutils.TestAddress( "op-C" )
228 testing.SetRealm(testing.NewCodeRealm(valopersRealmPath))
229
230 // First rotation: A → B. Read-modify-write of GetValsetEffective.
231 RotateValoperSigningKey(cross(cur), opA, pubKeyA, pubKeyB)
232
233 // Second rotation in the same block: C → A. Should not clobber
234 // the first rotation; should read proposed-when-dirty as baseline.
235 RotateValoperSigningKey(cross(cur), opC, pubKeyC, pubKeyA)
236
237 // Final effective set: {pubKeyB:10, pubKeyA:5}. Order may differ
238 // because GetValsetEffective parses sysparams' sorted-string slot
239 // and returns []Validator preserving that order.
240 effective := sysparams.GetValsetEffective()
241 urequire.Equal(t, 2 , len(effective))
242
243 // Build a power-by-pubkey map and assert.
244 powerOf := map [ string ] uint64 {}
245 for _, v := range effective {
246 powerOf[v.PubKey] = v.VotingPower
247 }
248 uassert.Equal(t, uint64 ( 10 ), powerOf[pubKeyB])
249 uassert.Equal(t, uint64 ( 5 ), powerOf[pubKeyA])
250 _, ok := powerOf[pubKeyC]
251 uassert.False(t, ok, "pubKeyC should be replaced by pubKeyA at the rotated slot" )
252 }
253