tendermint_test.gno
12.32 Kb · 333 lines
1package tendermint_test
2
3import (
4 "testing"
5 "time"
6
7 "gno.land/p/aib/ibc/lightclient"
8 "gno.land/p/aib/ibc/lightclient/tendermint"
9 "gno.land/p/aib/ibc/types"
10 "gno.land/p/aib/ics23"
11 "gno.land/p/nt/uassert/v0"
12 "gno.land/p/nt/urequire/v0"
13)
14
15func TestGetNeighboringConsensusStates(t *testing.T) {
16 tm := tendermint.NewTMLightClient()
17 nextValsHash := []byte("nextVals")
18 height01 := types.NewHeight(0, 1)
19 cs01 := &tendermint.ConsensusState{
20 Timestamp: time.Now().UTC(),
21 Root: tendermint.NewMerkleRoot([]byte("hash0-1")),
22 NextValidatorsHash: nextValsHash,
23 }
24 clientState := tendermint.NewClientState(chainID, tendermint.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height01, ics23.GetSDKProofSpecs(), upgradePath)
25 err := tm.Initialize(*clientState, *cs01)
26 urequire.NoError(t, err)
27
28 cs04 := &tendermint.ConsensusState{
29 Timestamp: time.Now().UTC(),
30 Root: tendermint.NewMerkleRoot([]byte("hash0-4")),
31 NextValidatorsHash: nextValsHash,
32 }
33 cs49 := &tendermint.ConsensusState{
34 Timestamp: time.Now().UTC(),
35 Root: tendermint.NewMerkleRoot([]byte("hash4-9")),
36 NextValidatorsHash: nextValsHash,
37 }
38 height04 := types.NewHeight(0, 4)
39 height49 := types.NewHeight(4, 9)
40 tm.SetConsensusState(height04, cs04)
41 tm.SetConsensusState(height49, cs49)
42
43 // Previous height01 should return nil
44 prevCs01, ok := tm.GetPreviousConsensusState(height01)
45 urequire.True(t, prevCs01 == nil, "consensus state exists before lowest consensus state")
46 urequire.False(t, ok)
47
48 // Previous from non existing height02 should return cs01
49 prevCs02, ok := tm.GetPreviousConsensusState(types.NewHeight(0, 2))
50 urequire.Equal(t, string(cs01.Root.Hash), string(prevCs02.Root.Hash),
51 "previous consensus state is not returned correctly")
52 urequire.True(t, ok)
53
54 // Previous from height49 should return cs04
55 prevCs49, ok := tm.GetPreviousConsensusState(height49)
56 urequire.Equal(t, string(cs04.Root.Hash), string(prevCs49.Root.Hash),
57 "previous consensus state is not returned correctly")
58 urequire.True(t, ok)
59
60 // Next from height01 should return cs04
61 nextCs01, ok := tm.GetNextConsensusState(height01)
62 urequire.Equal(t, string(cs04.Root.Hash), string(nextCs01.Root.Hash),
63 "next consensus state not returned correctly")
64 urequire.True(t, ok)
65
66 // Next from non existing height02 should return cs04
67 nextCs02, ok := tm.GetNextConsensusState(types.NewHeight(0, 2))
68 urequire.Equal(t, string(cs04.Root.Hash), string(nextCs02.Root.Hash),
69 "next consensus state not returned correctly")
70 urequire.True(t, ok)
71
72 // Next from height49 should return nil
73 nextCs49, ok := tm.GetNextConsensusState(height49)
74 urequire.True(t, nextCs49 == nil, "next consensus state exists after highest consensus state")
75 urequire.False(t, ok)
76}
77
78func TestRecoverClient(t *testing.T) {
79 newConsState := func(root string) *tendermint.ConsensusState {
80 return &tendermint.ConsensusState{
81 Timestamp: time.Now().UTC(),
82 Root: tendermint.NewMerkleRoot([]byte(root)),
83 NextValidatorsHash: []byte("nextVals"),
84 }
85 }
86 // Build two clients with matching immutable parameters, different chainIDs
87 // and different latest heights.
88 newSubject := func() *tendermint.TMLightClient {
89 tm := tendermint.NewTMLightClient()
90 cs := tendermint.NewClientState("subject-chain", tendermint.DefaultTrustLevel,
91 trustingPeriod, ubdPeriod, maxClockDrift, types.NewHeight(0, 5),
92 ics23.GetSDKProofSpecs(), upgradePath)
93 urequire.NoError(t, tm.Initialize(*cs, *newConsState("subject-root")))
94 // Freeze the subject so its status becomes Frozen (for realism; not
95 // required by RecoverClient itself).
96 tm.ClientState.FrozenHeight = tendermint.FrozenHeight
97 return tm
98 }
99 newSubstitute := func() *tendermint.TMLightClient {
100 tm := tendermint.NewTMLightClient()
101 cs := tendermint.NewClientState("substitute-chain", tendermint.DefaultTrustLevel,
102 trustingPeriod, ubdPeriod, maxClockDrift, types.NewHeight(0, 10),
103 ics23.GetSDKProofSpecs(), upgradePath)
104 urequire.NoError(t, tm.Initialize(*cs, *newConsState("substitute-root")))
105 return tm
106 }
107
108 t.Run("success", func(t *testing.T) {
109 subject := newSubject()
110 substitute := newSubstitute()
111
112 err := subject.RecoverClient(substitute)
113 urequire.NoError(t, err)
114
115 uassert.Equal(t, "substitute-chain", subject.ClientState.ChainID)
116 uassert.True(t, subject.ClientState.LatestHeight.EQ(types.NewHeight(0, 10)))
117 uassert.True(t, subject.ClientState.FrozenHeight.IsZero())
118 // Substitute consensus state at height (0,10) must have been copied.
119 cs, found := subject.GetConsensusState(types.NewHeight(0, 10))
120 urequire.True(t, found, "consensus state at substitute latest height not copied")
121 uassert.Equal(t, "substitute-root", string(cs.Root.Hash))
122 })
123
124 t.Run("failure: substitute is nil", func(t *testing.T) {
125 subject := newSubject()
126
127 err := subject.RecoverClient(nil)
128 urequire.Error(t, err)
129 urequire.ErrorContains(t, err, "substitute must not be nil")
130 })
131
132 t.Run("success: substitute TrustingPeriod is adopted", func(t *testing.T) {
133 subject := newSubject()
134 substitute := newSubstitute()
135 substitute.ClientState.TrustingPeriod = trustingPeriod + time.Hour
136
137 err := subject.RecoverClient(substitute)
138 urequire.NoError(t, err)
139 uassert.Equal(t, int64(trustingPeriod+time.Hour), int64(subject.ClientState.TrustingPeriod))
140 })
141
142 t.Run("failure: mismatching max clock drift", func(t *testing.T) {
143 subject := newSubject()
144 substitute := newSubstitute()
145 substitute.ClientState.MaxClockDrift = maxClockDrift + time.Second
146
147 err := subject.RecoverClient(substitute)
148 urequire.Error(t, err)
149 urequire.ErrorContains(t, err, "differ on MaxClockDrift")
150 })
151
152 t.Run("failure: mismatching trust level", func(t *testing.T) {
153 subject := newSubject()
154 substitute := newSubstitute()
155 substitute.ClientState.TrustLevel = tendermint.NewFraction(2, 3)
156
157 err := subject.RecoverClient(substitute)
158 urequire.Error(t, err)
159 urequire.ErrorContains(t, err, "differ on TrustLevel")
160 })
161
162 t.Run("failure: mismatching unbonding period", func(t *testing.T) {
163 subject := newSubject()
164 substitute := newSubstitute()
165 substitute.ClientState.UnbondingPeriod = ubdPeriod + time.Hour
166
167 err := subject.RecoverClient(substitute)
168 urequire.Error(t, err)
169 urequire.ErrorContains(t, err, "differ on UnbondingPeriod")
170 })
171
172 t.Run("failure: mismatching proof specs", func(t *testing.T) {
173 subject := newSubject()
174 substitute := newSubstitute()
175 substitute.ClientState.ProofSpecs = substitute.ClientState.ProofSpecs[:1]
176
177 err := subject.RecoverClient(substitute)
178 urequire.Error(t, err)
179 urequire.ErrorContains(t, err, "differ on ProofSpecs")
180 })
181
182 t.Run("failure: mismatching upgrade path", func(t *testing.T) {
183 subject := newSubject()
184 substitute := newSubstitute()
185 substitute.ClientState.UpgradePath = []string{"otherUpgrade"}
186
187 err := subject.RecoverClient(substitute)
188 urequire.Error(t, err)
189 urequire.ErrorContains(t, err, "differ on UpgradePath")
190 })
191
192 t.Run("failure: missing consensus state at substitute latest height", func(t *testing.T) {
193 subject := newSubject()
194 substitute := newSubstitute()
195 // Drop the consensus state that Initialize stored.
196 substitute.ConsensusStateByHeight.Remove(substitute.ClientState.LatestHeight.StringNatSort())
197
198 err := subject.RecoverClient(substitute)
199 urequire.Error(t, err)
200 urequire.ErrorContains(t, err, "substitute consensus state not found")
201 })
202
203 // Assertion that interface conformance holds when calling through the
204 // lightclient.Interface type (catches future signature drift).
205 t.Run("success via Interface", func(t *testing.T) {
206 subject := newSubject()
207 var substitute lightclient.Interface = newSubstitute()
208
209 err := subject.RecoverClient(substitute)
210 urequire.NoError(t, err)
211 })
212}
213
214func TestVerifyUpgradeAndUpdateStateFailures(t *testing.T) {
215 currentHeight := types.NewHeight(0, 5)
216 upgradedHeight := types.NewHeight(0, 10)
217
218 newClient := func() *tendermint.TMLightClient {
219 tm := tendermint.NewTMLightClient()
220 cs := tendermint.NewClientState(chainID, tendermint.DefaultTrustLevel,
221 trustingPeriod, ubdPeriod, maxClockDrift, currentHeight,
222 ics23.GetSDKProofSpecs(), upgradePath)
223 validHash := make([]byte, 32)
224 for i := range validHash {
225 validHash[i] = byte(i + 1)
226 }
227 consState := &tendermint.ConsensusState{
228 Timestamp: time.Now().UTC(),
229 Root: tendermint.NewMerkleRoot([]byte("current-root")),
230 NextValidatorsHash: validHash,
231 }
232 urequire.NoError(t, tm.Initialize(*cs, *consState))
233 return tm
234 }
235
236 validUpgradedClient := tendermint.ClientState{
237 ChainID: "chain-after-upgrade",
238 TrustLevel: tendermint.DefaultTrustLevel,
239 TrustingPeriod: trustingPeriod,
240 UnbondingPeriod: ubdPeriod,
241 MaxClockDrift: maxClockDrift,
242 LatestHeight: upgradedHeight,
243 ProofSpecs: ics23.GetSDKProofSpecs(),
244 UpgradePath: upgradePath,
245 }
246 thirtyTwoBytes := make([]byte, 32)
247 for i := range thirtyTwoBytes {
248 thirtyTwoBytes[i] = byte(i)
249 }
250 validUpgradedConsState := tendermint.ConsensusState{
251 Timestamp: time.Now().UTC(),
252 Root: tendermint.NewMerkleRoot([]byte("upgraded-root")),
253 NextValidatorsHash: thirtyTwoBytes,
254 }
255 dummyProof := []ics23.CommitmentProof{ics23.CommitmentProof_Exist{Exist: &ics23.ExistenceProof{}}}
256
257 t.Run("failure: newClient wrong type", func(t *testing.T) {
258 tm := newClient()
259 err := tm.VerifyUpgradeAndUpdateState("not-a-client-state", validUpgradedConsState, dummyProof, dummyProof)
260 urequire.Error(t, err)
261 urequire.ErrorContains(t, err, "must be tendermint.ClientState")
262 })
263
264 t.Run("failure: invalid upgraded client state", func(t *testing.T) {
265 tm := newClient()
266 bad := validUpgradedClient
267 bad.ChainID = "" // fails ValidateBasic
268 err := tm.VerifyUpgradeAndUpdateState(bad, validUpgradedConsState, dummyProof, dummyProof)
269 urequire.Error(t, err)
270 urequire.ErrorContains(t, err, "invalid upgraded client state")
271 })
272
273 t.Run("failure: newConsState wrong type", func(t *testing.T) {
274 tm := newClient()
275 err := tm.VerifyUpgradeAndUpdateState(validUpgradedClient, "not-a-consensus-state", dummyProof, dummyProof)
276 urequire.Error(t, err)
277 urequire.ErrorContains(t, err, "must be tendermint.ConsensusState")
278 })
279
280 t.Run("failure: invalid upgraded consensus state", func(t *testing.T) {
281 tm := newClient()
282 bad := validUpgradedConsState
283 bad.Root = tendermint.MerkleRoot{} // empty root → ValidateBasic fails
284 err := tm.VerifyUpgradeAndUpdateState(validUpgradedClient, bad, dummyProof, dummyProof)
285 urequire.Error(t, err)
286 urequire.ErrorContains(t, err, "invalid upgraded consensus state")
287 })
288
289 t.Run("failure: empty UpgradePath on current client", func(t *testing.T) {
290 tm := newClient()
291 tm.ClientState.UpgradePath = nil
292 err := tm.VerifyUpgradeAndUpdateState(validUpgradedClient, validUpgradedConsState, dummyProof, dummyProof)
293 urequire.Error(t, err)
294 urequire.ErrorContains(t, err, "no upgrade path set")
295 })
296
297 t.Run("failure: upgraded latest height not greater than current", func(t *testing.T) {
298 tm := newClient()
299 notGreater := validUpgradedClient
300 notGreater.LatestHeight = currentHeight
301 err := tm.VerifyUpgradeAndUpdateState(notGreater, validUpgradedConsState, dummyProof, dummyProof)
302 urequire.Error(t, err)
303 urequire.ErrorContains(t, err, "must be greater than current latest height")
304 })
305
306 t.Run("failure: clientProof wrong type", func(t *testing.T) {
307 tm := newClient()
308 err := tm.VerifyUpgradeAndUpdateState(validUpgradedClient, validUpgradedConsState, []byte("raw"), dummyProof)
309 urequire.Error(t, err)
310 urequire.ErrorContains(t, err, "upgradeClientProof must be []ics23.CommitmentProof")
311 })
312
313 t.Run("failure: clientProof empty", func(t *testing.T) {
314 tm := newClient()
315 err := tm.VerifyUpgradeAndUpdateState(validUpgradedClient, validUpgradedConsState, []ics23.CommitmentProof{}, dummyProof)
316 urequire.Error(t, err)
317 urequire.ErrorContains(t, err, "upgradeClientProof cannot be empty")
318 })
319
320 t.Run("failure: consensusProof wrong type", func(t *testing.T) {
321 tm := newClient()
322 err := tm.VerifyUpgradeAndUpdateState(validUpgradedClient, validUpgradedConsState, dummyProof, "not-a-proof")
323 urequire.Error(t, err)
324 urequire.ErrorContains(t, err, "upgradeConsensusStateProof must be []ics23.CommitmentProof")
325 })
326
327 t.Run("failure: consensusProof empty", func(t *testing.T) {
328 tm := newClient()
329 err := tm.VerifyUpgradeAndUpdateState(validUpgradedClient, validUpgradedConsState, dummyProof, []ics23.CommitmentProof{})
330 urequire.Error(t, err)
331 urequire.ErrorContains(t, err, "upgradeConsensusStateProof cannot be empty")
332 })
333}