Skip to content

Commit cb63ccb

Browse files
author
beer-1
committed
fix hasSameAttestorsAndThreshold and write testcases
1 parent 858b24c commit cb63ccb

2 files changed

Lines changed: 322 additions & 6 deletions

File tree

x/ibc/light-clients/07-tendermint-attestor/client_state.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -316,14 +316,21 @@ func (cs ClientState) hasSameAttestorsAndThreshold(cs2 ClientState) bool {
316316
if len(pubkeys1) != len(pubkeys2) {
317317
return false
318318
}
319-
320-
for _, pubkey := range pubkeys1 {
321-
if !slices.ContainsFunc(pubkeys2, func(pubkey2 cryptotypes.PubKey) bool {
322-
return pubkey.Equals(pubkey2)
323-
}) {
319+
count1 := make(map[string]int, len(pubkeys1))
320+
for _, pk := range pubkeys1 {
321+
count1[pk.Address().String()]++
322+
}
323+
count2 := make(map[string]int, len(pubkeys2))
324+
for _, pk := range pubkeys2 {
325+
count2[pk.Address().String()]++
326+
}
327+
if len(count1) != len(count2) {
328+
return false
329+
}
330+
for k, n := range count1 {
331+
if count2[k] != n {
324332
return false
325333
}
326334
}
327-
328335
return true
329336
}
Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
package tendermintattestor
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
8+
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
9+
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
10+
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
11+
clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types"
12+
commitmenttypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types"
13+
tmlightclient "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint"
14+
)
15+
16+
func Test_hasSameAttestorsAndThreshold(t *testing.T) {
17+
// Generate test public keys
18+
pubkey1 := secp256k1.GenPrivKey().PubKey()
19+
pubkey2 := secp256k1.GenPrivKey().PubKey()
20+
pubkey3 := secp256k1.GenPrivKey().PubKey()
21+
pubkey4 := secp256k1.GenPrivKey().PubKey()
22+
23+
// Helper function to create attestor pubkeys
24+
createAttestorPubkeys := func(pubkeys ...cryptotypes.PubKey) []*codectypes.Any {
25+
result := make([]*codectypes.Any, len(pubkeys))
26+
for i, pk := range pubkeys {
27+
any, err := codectypes.NewAnyWithValue(pk)
28+
require.NoError(t, err)
29+
result[i] = any
30+
}
31+
return result
32+
}
33+
34+
// Helper function to create client state
35+
createClientState := func(attestorPubkeys []*codectypes.Any, threshold uint32) ClientState {
36+
return ClientState{
37+
ClientState: &tmlightclient.ClientState{
38+
ChainId: "test-chain",
39+
TrustLevel: tmlightclient.Fraction{Numerator: 1, Denominator: 3},
40+
TrustingPeriod: 86400,
41+
UnbondingPeriod: 172800,
42+
MaxClockDrift: 1000,
43+
LatestHeight: clienttypes.Height{RevisionNumber: 1, RevisionHeight: 100},
44+
ProofSpecs: commitmenttypes.GetSDKSpecs(),
45+
UpgradePath: []string{"upgrade", "upgradedClient"},
46+
},
47+
AttestorPubkeys: attestorPubkeys,
48+
Threshold: threshold,
49+
}
50+
}
51+
52+
tests := []struct {
53+
name string
54+
cs1 ClientState
55+
cs2 ClientState
56+
expected bool
57+
}{
58+
{
59+
name: "same attestors and threshold",
60+
cs1: createClientState(
61+
createAttestorPubkeys(pubkey1, pubkey2, pubkey3),
62+
2,
63+
),
64+
cs2: createClientState(
65+
createAttestorPubkeys(pubkey1, pubkey2, pubkey3),
66+
2,
67+
),
68+
expected: true,
69+
},
70+
{
71+
name: "same attestors in different order and threshold",
72+
cs1: createClientState(
73+
createAttestorPubkeys(pubkey1, pubkey2, pubkey3),
74+
2,
75+
),
76+
cs2: createClientState(
77+
createAttestorPubkeys(pubkey3, pubkey1, pubkey2),
78+
2,
79+
),
80+
expected: true,
81+
},
82+
{
83+
name: "different threshold",
84+
cs1: createClientState(
85+
createAttestorPubkeys(pubkey1, pubkey2, pubkey3),
86+
2,
87+
),
88+
cs2: createClientState(
89+
createAttestorPubkeys(pubkey1, pubkey2, pubkey3),
90+
3,
91+
),
92+
expected: false,
93+
},
94+
{
95+
name: "different number of attestors",
96+
cs1: createClientState(
97+
createAttestorPubkeys(pubkey1, pubkey2, pubkey3),
98+
2,
99+
),
100+
cs2: createClientState(
101+
createAttestorPubkeys(pubkey1, pubkey2),
102+
2,
103+
),
104+
expected: false,
105+
},
106+
{
107+
name: "different attestor sets",
108+
cs1: createClientState(
109+
createAttestorPubkeys(pubkey1, pubkey2, pubkey3),
110+
2,
111+
),
112+
cs2: createClientState(
113+
createAttestorPubkeys(pubkey1, pubkey2, pubkey4),
114+
2,
115+
),
116+
expected: false,
117+
},
118+
{
119+
name: "duplicate attestors in first set",
120+
cs1: createClientState(
121+
createAttestorPubkeys(pubkey1, pubkey1, pubkey2),
122+
2,
123+
),
124+
cs2: createClientState(
125+
createAttestorPubkeys(pubkey1, pubkey2, pubkey3),
126+
2,
127+
),
128+
expected: false,
129+
},
130+
{
131+
name: "duplicate attestors in second set",
132+
cs1: createClientState(
133+
createAttestorPubkeys(pubkey1, pubkey2, pubkey3),
134+
2,
135+
),
136+
cs2: createClientState(
137+
createAttestorPubkeys(pubkey1, pubkey2, pubkey2),
138+
2,
139+
),
140+
expected: false,
141+
},
142+
{
143+
name: "empty attestor sets with same threshold",
144+
cs1: createClientState(
145+
[]*codectypes.Any{},
146+
0,
147+
),
148+
cs2: createClientState(
149+
[]*codectypes.Any{},
150+
0,
151+
),
152+
expected: true,
153+
},
154+
{
155+
name: "empty attestor sets with different threshold",
156+
cs1: createClientState(
157+
[]*codectypes.Any{},
158+
0,
159+
),
160+
cs2: createClientState(
161+
[]*codectypes.Any{},
162+
1,
163+
),
164+
expected: false,
165+
},
166+
{
167+
name: "single attestor with same threshold",
168+
cs1: createClientState(
169+
createAttestorPubkeys(pubkey1),
170+
1,
171+
),
172+
cs2: createClientState(
173+
createAttestorPubkeys(pubkey1),
174+
1,
175+
),
176+
expected: true,
177+
},
178+
{
179+
name: "single attestor with different threshold",
180+
cs1: createClientState(
181+
createAttestorPubkeys(pubkey1),
182+
1,
183+
),
184+
cs2: createClientState(
185+
createAttestorPubkeys(pubkey1),
186+
2,
187+
),
188+
expected: false,
189+
},
190+
{
191+
name: "same attestors but different counts (duplicates)",
192+
cs1: createClientState(
193+
createAttestorPubkeys(pubkey1, pubkey2, pubkey3),
194+
2,
195+
),
196+
cs2: createClientState(
197+
createAttestorPubkeys(pubkey1, pubkey2, pubkey3, pubkey3),
198+
2,
199+
),
200+
expected: false,
201+
},
202+
}
203+
204+
for _, tt := range tests {
205+
t.Run(tt.name, func(t *testing.T) {
206+
result := tt.cs1.hasSameAttestorsAndThreshold(tt.cs2)
207+
require.Equal(t, tt.expected, result, "test case: %s", tt.name)
208+
})
209+
}
210+
}
211+
212+
// Test_hasSameAttestorsAndThreshold_EdgeCases tests edge cases and boundary conditions
213+
func Test_hasSameAttestorsAndThreshold_EdgeCases(t *testing.T) {
214+
// Generate test public keys
215+
pubkey1 := secp256k1.GenPrivKey().PubKey()
216+
pubkey2 := secp256k1.GenPrivKey().PubKey()
217+
218+
// Helper function to create attestor pubkeys
219+
createAttestorPubkeys := func(pubkeys ...cryptotypes.PubKey) []*codectypes.Any {
220+
result := make([]*codectypes.Any, len(pubkeys))
221+
for i, pk := range pubkeys {
222+
any, err := codectypes.NewAnyWithValue(pk)
223+
require.NoError(t, err)
224+
result[i] = any
225+
}
226+
return result
227+
}
228+
229+
// Helper function to create client state
230+
createClientState := func(attestorPubkeys []*codectypes.Any, threshold uint32) ClientState {
231+
return ClientState{
232+
ClientState: &tmlightclient.ClientState{
233+
ChainId: "test-chain",
234+
TrustLevel: tmlightclient.Fraction{Numerator: 1, Denominator: 3},
235+
TrustingPeriod: 86400,
236+
UnbondingPeriod: 172800,
237+
MaxClockDrift: 1000,
238+
LatestHeight: clienttypes.Height{RevisionNumber: 1, RevisionHeight: 100},
239+
ProofSpecs: commitmenttypes.GetSDKSpecs(),
240+
UpgradePath: []string{"upgrade", "upgradedClient"},
241+
},
242+
AttestorPubkeys: attestorPubkeys,
243+
Threshold: threshold,
244+
}
245+
}
246+
247+
tests := []struct {
248+
name string
249+
cs1 ClientState
250+
cs2 ClientState
251+
expected bool
252+
}{
253+
{
254+
name: "zero threshold with empty attestors",
255+
cs1: createClientState(
256+
[]*codectypes.Any{},
257+
0,
258+
),
259+
cs2: createClientState(
260+
[]*codectypes.Any{},
261+
0,
262+
),
263+
expected: true,
264+
},
265+
{
266+
name: "zero threshold with non-empty attestors",
267+
cs1: createClientState(
268+
createAttestorPubkeys(pubkey1),
269+
0,
270+
),
271+
cs2: createClientState(
272+
createAttestorPubkeys(pubkey1),
273+
0,
274+
),
275+
expected: true,
276+
},
277+
{
278+
name: "threshold equals attestor count",
279+
cs1: createClientState(
280+
createAttestorPubkeys(pubkey1, pubkey2),
281+
2,
282+
),
283+
cs2: createClientState(
284+
createAttestorPubkeys(pubkey1, pubkey2),
285+
2,
286+
),
287+
expected: true,
288+
},
289+
{
290+
name: "threshold greater than attestor count",
291+
cs1: createClientState(
292+
createAttestorPubkeys(pubkey1),
293+
2,
294+
),
295+
cs2: createClientState(
296+
createAttestorPubkeys(pubkey1),
297+
2,
298+
),
299+
expected: true,
300+
},
301+
}
302+
303+
for _, tt := range tests {
304+
t.Run(tt.name, func(t *testing.T) {
305+
result := tt.cs1.hasSameAttestorsAndThreshold(tt.cs2)
306+
require.Equal(t, tt.expected, result, "test case: %s", tt.name)
307+
})
308+
}
309+
}

0 commit comments

Comments
 (0)