Search Apps Documentation Source Content File Folder Download Copy Actions Download

cache_test.gno

8.12 Kb · 252 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// 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.
 17var cur realm
 18
 19// resetCache clears valoperCache so subtests don't leak state.
 20func resetCache() {
 21	valoperCache = bptree.NewBPTree32()
 22}
 23
 24func 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, ok := valoperCache.Get(op.String())
 36	urequire.True(t, ok, "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, ok = valoperCache.Get(op.String())
 47	urequire.True(t, ok)
 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
 54func 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
 75func 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
 97func 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
114func 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
141func 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
169func 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
195func 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
206func 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
216func 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}