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