rotate_test.gno
5.67 Kb · 160 lines
1package valopers
2
3import (
4 "chain"
5 "testing"
6
7 "gno.land/p/nt/testutils/v0"
8 "gno.land/p/nt/uassert/v0"
9 "gno.land/p/nt/urequire/v0"
10)
11
12const (
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.
26func registerForRotation(t *testing.T) struct {
27 Moniker string
28 Description string
29 ServerType string
30 Address address
31 PubKey string
32} {
33 t.Helper()
34 resetState()
35 info := validValidatorInfo(t)
36 testing.SetRealm(testing.NewUserRealm(info.Address))
37 testing.SetOriginSend(chain.Coins{minFee})
38 Register(cross, info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
39 return info
40}
41
42func TestUpdateSigningKey_HappyPath(t *testing.T) {
43 info := registerForRotation(t)
44
45 // Advance past the throttle window. SkipHeights doesn't preserve
46 // the realm context across the boundary, so re-set it before the
47 // next cross-call.
48 testing.SkipHeights(testRotationPeriodBlocks + 1)
49 testing.SetRealm(testing.NewUserRealm(info.Address))
50
51 uassert.NotAborts(t, func() {
52 UpdateSigningKey(cross, info.Address, rotateTargetPubKey)
53 })
54
55 v := GetByAddr(info.Address)
56 uassert.Equal(t, rotateTargetPubKey, v.SigningPubKey)
57
58 newSigningAddr, err := chain.PubKeyAddress(rotateTargetPubKey)
59 urequire.NoError(t, err)
60 uassert.Equal(t, newSigningAddr, v.SigningAddress)
61
62 // New entry active in registry.
63 rawNew, ok := signingRegistry.Get(newSigningAddr.String())
64 urequire.True(t, ok, "new signing addr in registry")
65 newEntry := rawNew.(regEntry)
66 uassert.Equal(t, info.Address, newEntry.OperatorAddress)
67 uassert.Equal(t, int64(0), newEntry.RetiredAtHeight)
68
69 // Old entry retired (still present, RetiredAtHeight > 0).
70 oldSigningAddr, err := chain.PubKeyAddress(info.PubKey)
71 urequire.NoError(t, err)
72 rawOld, ok := signingRegistry.Get(oldSigningAddr.String())
73 urequire.True(t, ok, "old signing addr retained as retired")
74 oldEntry := rawOld.(regEntry)
75 uassert.Equal(t, info.Address, oldEntry.OperatorAddress)
76 uassert.True(t, oldEntry.RetiredAtHeight > 0, "old entry must be retired (RetiredAtHeight > 0)")
77}
78
79func TestUpdateSigningKey_ThrottleRejection(t *testing.T) {
80 info := registerForRotation(t)
81 testing.SetRealm(testing.NewUserRealm(info.Address))
82
83 // Try to rotate immediately, before throttle elapses.
84 uassert.AbortsWithMessage(t, ErrRotationThrottled.Error(), func() {
85 UpdateSigningKey(cross, info.Address, rotateTargetPubKey)
86 })
87
88 // Advance just shy of the threshold; still rejected.
89 testing.SkipHeights(testRotationPeriodBlocks - 1)
90 testing.SetRealm(testing.NewUserRealm(info.Address))
91 uassert.AbortsWithMessage(t, ErrRotationThrottled.Error(), func() {
92 UpdateSigningKey(cross, info.Address, rotateTargetPubKey)
93 })
94
95 // One more block — now exactly at the threshold; allowed.
96 testing.SkipHeights(1)
97 testing.SetRealm(testing.NewUserRealm(info.Address))
98 uassert.NotAborts(t, func() {
99 UpdateSigningKey(cross, info.Address, rotateTargetPubKey)
100 })
101}
102
103func TestUpdateSigningKey_RejectsNonAuthListCaller(t *testing.T) {
104 info := registerForRotation(t)
105 testing.SkipHeights(testRotationPeriodBlocks + 1)
106
107 // Switch to a caller not on the auth list.
108 testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("attacker")))
109
110 // Pin the exact authorizable error so this catches an auth
111 // regression rather than any abort. Empty-substring match (the
112 // previous form) accepted any panic, including unrelated ones.
113 uassert.AbortsContains(t, "not in authorized list", func() {
114 UpdateSigningKey(cross, info.Address, rotateTargetPubKey)
115 })
116}
117
118func TestUpdateSigningKey_RejectsReuseOfActiveKey(t *testing.T) {
119 info := registerForRotation(t)
120 testing.SkipHeights(testRotationPeriodBlocks + 1)
121 testing.SetRealm(testing.NewUserRealm(info.Address))
122
123 // Try to "rotate" to the same key — already in registry.
124 uassert.AbortsWithMessage(t, ErrSigningKeyTaken.Error(), func() {
125 UpdateSigningKey(cross, info.Address, info.PubKey)
126 })
127}
128
129func TestUpdateSigningKey_RejectsReuseOfRetiredKey(t *testing.T) {
130 info := registerForRotation(t)
131
132 // First rotation: info.PubKey -> rotateTargetPubKey. info.PubKey
133 // becomes retired.
134 testing.SkipHeights(testRotationPeriodBlocks + 1)
135 testing.SetRealm(testing.NewUserRealm(info.Address))
136 UpdateSigningKey(cross, info.Address, rotateTargetPubKey)
137
138 // Second rotation back to info.PubKey must fail — it's retired
139 // but signingRegistry retains it forever.
140 testing.SkipHeights(testRotationPeriodBlocks + 1)
141 testing.SetRealm(testing.NewUserRealm(info.Address))
142 uassert.AbortsWithMessage(t, ErrSigningKeyTaken.Error(), func() {
143 UpdateSigningKey(cross, info.Address, info.PubKey)
144 })
145}
146
147func TestUpdateSigningKey_LastRotationHeightAdvances(t *testing.T) {
148 info := registerForRotation(t)
149 testing.SkipHeights(testRotationPeriodBlocks + 1)
150 testing.SetRealm(testing.NewUserRealm(info.Address))
151 UpdateSigningKey(cross, info.Address, rotateTargetPubKey)
152
153 v := GetByAddr(info.Address)
154 uassert.True(t, v.LastRotationHeight > 0, "LastRotationHeight set on rotation")
155
156 // Immediately after, throttle rejects again.
157 uassert.AbortsWithMessage(t, ErrRotationThrottled.Error(), func() {
158 UpdateSigningKey(cross, info.Address, "gpub1pgfj7ard9eg82cjtv4u4xetrwqer2dntxyfzxz3pqddddqg2glc8x4fl7vxjlnr7p5a3czm5kcdp4239sg6yqdc4rc2r5cjrffs")
159 })
160}