Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}