Kind Realm (renderable)
Name valopers
Namespace gnops
Exported functions 13
Module gno.land/r/gnops/valopers
gno 0.9 rotate_test.gno gno
⧉
1 package valopers 2 3 import ( 4 "chain" 5 "testing" 6 7 "gno.land/p/nt/testutils/v0" 8 AddToAuthList (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}, addr string, member string)
Auth () string
DeleteFromAuthList (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}, addr string, member string)
GetByAddr (addr string) struct{Moniker string; Description string; ServerType string; OperatorAddress .uverse.address; SigningPubKey string; SigningAddress .uverse.address; LastRotationHeight int64; KeepRunning bool; auth *gno.land/p/nt/ownable/exts/authorizable/v0.Authorizable}
NewAuthorityRotationProposalRequest (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}, newContractPath string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
NewInstructionsProposalRequest (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}, newInstructions string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
Register (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}, moniker string, description string, serverType string, addr string, pubKey string)
Render (fullPath string) string
UpdateDescription (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}, addr string, description string)
UpdateKeepRunning (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}, addr string, keepRunning bool)
UpdateMoniker (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}, addr string, moniker string)
UpdateServerType (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}, addr string, serverType string)
UpdateSigningKey (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}, addr string, newPubKey string)
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
"gno.land/p/nt/uassert/v0"
9 "gno.land/p/nt/urequire/v0"
10 )
11
12 const (
13 // Second valid pubkey used as the rotation target. Distinct from
14 // validValidatorInfo's pubkey so the signingRegistry uniqueness
15 // check is exercised.
16 rotateTargetPubKey = "gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zq3ds6sdvc0shfkq02h6xx5g0jp04aadexfnpsmgjxu72xz9y30aqfrlpny"
17
18 // Test-local mirror of the rotation_period_blocks sys-param's
19 // default. resetState() seeds the same value into sysparams.
20 testRotationPeriodBlocks = int64 ( 600 )
21 )
22
23 // registerForRotation does the boilerplate setup that every rotation
24 // test starts from: clear state, register the valoper as info.Address,
25 // and return the info struct. rlm is threaded into Register via
26 // cross(rlm) — it's a non-first parameter so the helper stays a
27 // regular (non-crossing) function.
28 func registerForRotation(t *testing.T, rlm realm) struct {
29 Moniker string
30 Description string
31 ServerType string
32 Address address
33 PubKey string
34 } {
35 t.Helper()
36 resetState()
37 info := validValidatorInfo(t)
38 testing.SetRealm(testing.NewUserRealm(info.Address))
39 testing.SetOriginSend(chain.Coins{minFee})
40 Register(cross(rlm), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
41 return info
42 }
43
44 func TestUpdateSigningKey_HappyPath(cur realm, t *testing.T) {
45 info := registerForRotation(t, cur)
46
47 // Advance past the throttle window. SkipHeights doesn't preserve
48 // the realm context across the boundary, so re-set it before the
49 // next cross-call.
50 testing.SkipHeights(testRotationPeriodBlocks + 1 )
51 testing.SetRealm(testing.NewUserRealm(info.Address))
52
53 uassert.NotAborts(t, cur, func () {
54 UpdateSigningKey(cross(cur), info.Address, rotateTargetPubKey)
55 })
56
57 v := GetByAddr(info.Address)
58 uassert.Equal(t, rotateTargetPubKey, v.SigningPubKey)
59
60 newSigningAddr, err := chain.PubKeyAddress(rotateTargetPubKey)
61 urequire.NoError(t, err)
62 uassert.Equal(t, newSigningAddr, v.SigningAddress)
63
64 // New entry active in registry.
65 rawNew := signingRegistry.Get(newSigningAddr.String())
66 urequire.NotNil(t, rawNew, "new signing addr in registry" )
67 newEntry := rawNew.(regEntry)
68 uassert.Equal(t, info.Address, newEntry.OperatorAddress)
69 uassert.Equal(t, int64 ( 0 ), newEntry.RetiredAtHeight)
70
71 // Old entry retired (still present, RetiredAtHeight > 0).
72 oldSigningAddr, err := chain.PubKeyAddress(info.PubKey)
73 urequire.NoError(t, err)
74 rawOld := signingRegistry.Get(oldSigningAddr.String())
75 urequire.NotNil(t, rawOld, "old signing addr retained as retired" )
76 oldEntry := rawOld.(regEntry)
77 uassert.Equal(t, info.Address, oldEntry.OperatorAddress)
78 uassert.True(t, oldEntry.RetiredAtHeight > 0 , "old entry must be retired (RetiredAtHeight > 0)" )
79 }
80
81 func TestUpdateSigningKey_ThrottleRejection(cur realm, t *testing.T) {
82 info := registerForRotation(t, cur)
83 testing.SetRealm(testing.NewUserRealm(info.Address))
84
85 // Try to rotate immediately, before throttle elapses.
86 uassert.AbortsWithMessage(t, cur, ErrRotationThrottled.Error(), func () {
87 UpdateSigningKey(cross(cur), info.Address, rotateTargetPubKey)
88 })
89
90 // Advance just shy of the threshold; still rejected.
91 testing.SkipHeights(testRotationPeriodBlocks - 1 )
92 testing.SetRealm(testing.NewUserRealm(info.Address))
93 uassert.AbortsWithMessage(t, cur, ErrRotationThrottled.Error(), func () {
94 UpdateSigningKey(cross(cur), info.Address, rotateTargetPubKey)
95 })
96
97 // One more block — now exactly at the threshold; allowed.
98 testing.SkipHeights( 1 )
99 testing.SetRealm(testing.NewUserRealm(info.Address))
100 uassert.NotAborts(t, cur, func () {
101 UpdateSigningKey(cross(cur), info.Address, rotateTargetPubKey)
102 })
103 }
104
105 func TestUpdateSigningKey_RejectsNonAuthListCaller(cur realm, t *testing.T) {
106 info := registerForRotation(t, cur)
107 testing.SkipHeights(testRotationPeriodBlocks + 1 )
108
109 // Switch to a caller not on the auth list.
110 testing.SetRealm(testing.NewUserRealm(testutils.TestAddress( "attacker" )))
111
112 // Pin the exact authorizable error so this catches an auth
113 // regression rather than any abort. Empty-substring match (the
114 // previous form) accepted any panic, including unrelated ones.
115 uassert.AbortsContains(t, cur, "not in authorized list" , func () {
116 UpdateSigningKey(cross(cur), info.Address, rotateTargetPubKey)
117 })
118 }
119
120 func TestUpdateSigningKey_RejectsReuseOfActiveKey(cur realm, t *testing.T) {
121 info := registerForRotation(t, cur)
122 testing.SkipHeights(testRotationPeriodBlocks + 1 )
123 testing.SetRealm(testing.NewUserRealm(info.Address))
124
125 // Try to "rotate" to the same key — already in registry.
126 uassert.AbortsWithMessage(t, cur, ErrSigningKeyTaken.Error(), func () {
127 UpdateSigningKey(cross(cur), info.Address, info.PubKey)
128 })
129 }
130
131 func TestUpdateSigningKey_RejectsRotationOntoActiveValidator(cur realm, t *testing.T) {
132 // Front-running guard: a rotation whose derived signing address is
133 // already an active validator must be rejected. Mirrors the same
134 // guard in Register (valopers.gno: ErrFrontrunValidator). Without
135 // this, any valoper could rotate onto an unregistered (e.g.
136 // genesis-seeded) validator slot — v0's executor would overwrite
137 // the active validator's entry in the effective valset with this
138 // operator's claim, then a subsequent govDAO remove-op proposal
139 // would delete it. signingRegistry uniqueness alone doesn't catch
140 // this case: a genesis-seeded validator never went through
141 // Register or UpdateSigningKey, so its signing address is absent
142 // from signingRegistry.
143 info := registerForRotation(t, cur)
144
145 // Seed v0's valset:current with the address that rotateTargetPubKey
146 // derives to. ValsetEffective is read from valset:current when the
147 // dirty flag is unset, which is the default.
148 testing.SetSysParamStrings( "node" , "valset" , "current" ,
149 [] string {rotateTargetPubKey + ":1" })
150
151 testing.SkipHeights(testRotationPeriodBlocks + 1 )
152 testing.SetRealm(testing.NewUserRealm(info.Address))
153
154 uassert.AbortsWithMessage(t, cur, ErrFrontrunValidator.Error(), func () {
155 UpdateSigningKey(cross(cur), info.Address, rotateTargetPubKey)
156 })
157
158 // Cleanup: clear the seeded valset to avoid leaking into sibling
159 // subtests if the runner doesn't reset package state between them.
160 testing.SetSysParamStrings( "node" , "valset" , "current" , [] string {})
161 }
162
163 func TestUpdateSigningKey_RejectsReuseOfRetiredKey(cur realm, t *testing.T) {
164 info := registerForRotation(t, cur)
165
166 // First rotation: info.PubKey -> rotateTargetPubKey. info.PubKey
167 // becomes retired.
168 testing.SkipHeights(testRotationPeriodBlocks + 1 )
169 testing.SetRealm(testing.NewUserRealm(info.Address))
170 UpdateSigningKey(cross(cur), info.Address, rotateTargetPubKey)
171
172 // Second rotation back to info.PubKey must fail — it's retired
173 // but signingRegistry retains it forever.
174 testing.SkipHeights(testRotationPeriodBlocks + 1 )
175 testing.SetRealm(testing.NewUserRealm(info.Address))
176 uassert.AbortsWithMessage(t, cur, ErrSigningKeyTaken.Error(), func () {
177 UpdateSigningKey(cross(cur), info.Address, info.PubKey)
178 })
179 }
180
181 func TestUpdateSigningKey_LastRotationHeightAdvances(cur realm, t *testing.T) {
182 info := registerForRotation(t, cur)
183 testing.SkipHeights(testRotationPeriodBlocks + 1 )
184 testing.SetRealm(testing.NewUserRealm(info.Address))
185 UpdateSigningKey(cross(cur), info.Address, rotateTargetPubKey)
186
187 v := GetByAddr(info.Address)
188 uassert.True(t, v.LastRotationHeight > 0 , "LastRotationHeight set on rotation" )
189
190 // Immediately after, throttle rejects again.
191 uassert.AbortsWithMessage(t, cur, ErrRotationThrottled.Error(), func () {
192 UpdateSigningKey(cross(cur), info.Address, "gpub1pgfj7ard9eg82cjtv4u4xetrwqer2dntxyfzxz3pqddddqg2glc8x4fl7vxjlnr7p5a3czm5kcdp4239sg6yqdc4rc2r5cjrffs" )
193 })
194 }
195
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.