github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/types/evidence_test.go (about) 1 package types 2 3 import ( 4 "context" 5 "encoding/hex" 6 "math" 7 mrand "math/rand" 8 "testing" 9 "time" 10 11 "github.com/stretchr/testify/assert" 12 "github.com/stretchr/testify/require" 13 14 "github.com/ari-anchor/sei-tendermint/crypto" 15 "github.com/ari-anchor/sei-tendermint/crypto/ed25519" 16 tmrand "github.com/ari-anchor/sei-tendermint/libs/rand" 17 tmproto "github.com/ari-anchor/sei-tendermint/proto/tendermint/types" 18 "github.com/ari-anchor/sei-tendermint/version" 19 ) 20 21 var defaultVoteTime = time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC) 22 23 func TestEvidenceList(t *testing.T) { 24 ctx, cancel := context.WithCancel(context.Background()) 25 defer cancel() 26 27 ev := randomDuplicateVoteEvidence(ctx, t) 28 evl := EvidenceList([]Evidence{ev}) 29 30 assert.NotNil(t, evl.Hash()) 31 assert.True(t, evl.Has(ev)) 32 assert.False(t, evl.Has(&DuplicateVoteEvidence{})) 33 } 34 35 // TestEvidenceListProtoBuf to ensure parity in protobuf output and input 36 func TestEvidenceListProtoBuf(t *testing.T) { 37 ctx, cancel := context.WithCancel(context.Background()) 38 defer cancel() 39 40 const chainID = "mychain" 41 ev, err := NewMockDuplicateVoteEvidence(ctx, math.MaxInt64, time.Now(), chainID) 42 require.NoError(t, err) 43 data := EvidenceList{ev} 44 testCases := []struct { 45 msg string 46 data1 *EvidenceList 47 expPass1 bool 48 expPass2 bool 49 }{ 50 {"success", &data, true, true}, 51 {"empty evidenceData", &EvidenceList{}, true, true}, 52 {"fail nil Data", nil, false, false}, 53 } 54 55 for _, tc := range testCases { 56 protoData, err := tc.data1.ToProto() 57 if tc.expPass1 { 58 require.NoError(t, err, tc.msg) 59 } else { 60 require.Error(t, err, tc.msg) 61 } 62 63 eviD := new(EvidenceList) 64 err = eviD.FromProto(protoData) 65 if tc.expPass2 { 66 require.NoError(t, err, tc.msg) 67 require.Equal(t, tc.data1, eviD, tc.msg) 68 } else { 69 require.Error(t, err, tc.msg) 70 } 71 } 72 } 73 func randomDuplicateVoteEvidence(ctx context.Context, t *testing.T) *DuplicateVoteEvidence { 74 t.Helper() 75 val := NewMockPV() 76 blockID := makeBlockID([]byte("blockhash"), 1000, []byte("partshash")) 77 blockID2 := makeBlockID([]byte("blockhash2"), 1000, []byte("partshash")) 78 const chainID = "mychain" 79 return &DuplicateVoteEvidence{ 80 VoteA: makeVote(ctx, t, val, chainID, 0, 10, 2, 1, blockID, defaultVoteTime), 81 VoteB: makeVote(ctx, t, val, chainID, 0, 10, 2, 1, blockID2, defaultVoteTime.Add(1*time.Minute)), 82 TotalVotingPower: 30, 83 ValidatorPower: 10, 84 Timestamp: defaultVoteTime, 85 } 86 } 87 88 func TestDuplicateVoteEvidence(t *testing.T) { 89 const height = int64(13) 90 ctx, cancel := context.WithCancel(context.Background()) 91 defer cancel() 92 93 ev, err := NewMockDuplicateVoteEvidence(ctx, height, time.Now(), "mock-chain-id") 94 require.NoError(t, err) 95 assert.Equal(t, ev.Hash(), crypto.Checksum(ev.Bytes())) 96 assert.NotNil(t, ev.String()) 97 assert.Equal(t, ev.Height(), height) 98 } 99 100 func TestDuplicateVoteEvidenceValidation(t *testing.T) { 101 val := NewMockPV() 102 blockID := makeBlockID(crypto.Checksum([]byte("blockhash")), math.MaxInt32, crypto.Checksum([]byte("partshash"))) 103 blockID2 := makeBlockID(crypto.Checksum([]byte("blockhash2")), math.MaxInt32, crypto.Checksum([]byte("partshash"))) 104 const chainID = "mychain" 105 106 ctx, cancel := context.WithCancel(context.Background()) 107 defer cancel() 108 109 testCases := []struct { 110 testName string 111 malleateEvidence func(*DuplicateVoteEvidence) 112 expectErr bool 113 }{ 114 {"Good DuplicateVoteEvidence", func(ev *DuplicateVoteEvidence) {}, false}, 115 {"Nil vote A", func(ev *DuplicateVoteEvidence) { ev.VoteA = nil }, true}, 116 {"Nil vote B", func(ev *DuplicateVoteEvidence) { ev.VoteB = nil }, true}, 117 {"Nil votes", func(ev *DuplicateVoteEvidence) { 118 ev.VoteA = nil 119 ev.VoteB = nil 120 }, true}, 121 {"Invalid vote type", func(ev *DuplicateVoteEvidence) { 122 ev.VoteA = makeVote(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0, blockID2, defaultVoteTime) 123 }, true}, 124 {"Invalid vote order", func(ev *DuplicateVoteEvidence) { 125 swap := ev.VoteA.Copy() 126 ev.VoteA = ev.VoteB.Copy() 127 ev.VoteB = swap 128 }, true}, 129 } 130 for _, tc := range testCases { 131 tc := tc 132 t.Run(tc.testName, func(t *testing.T) { 133 vote1 := makeVote(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0x02, blockID, defaultVoteTime) 134 vote2 := makeVote(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0x02, blockID2, defaultVoteTime) 135 valSet := NewValidatorSet([]*Validator{val.ExtractIntoValidator(ctx, 10)}) 136 ev, err := NewDuplicateVoteEvidence(vote1, vote2, defaultVoteTime, valSet) 137 require.NoError(t, err) 138 tc.malleateEvidence(ev) 139 assert.Equal(t, tc.expectErr, ev.ValidateBasic() != nil, "Validate Basic had an unexpected result") 140 }) 141 } 142 } 143 144 func TestLightClientAttackEvidenceBasic(t *testing.T) { 145 ctx, cancel := context.WithCancel(context.Background()) 146 defer cancel() 147 148 height := int64(5) 149 commonHeight := height - 1 150 nValidators := 10 151 voteSet, valSet, privVals := randVoteSet(ctx, t, height, 1, tmproto.PrecommitType, nValidators, 1) 152 153 header := makeHeaderRandom() 154 header.Height = height 155 blockID := makeBlockID(crypto.Checksum([]byte("blockhash")), math.MaxInt32, crypto.Checksum([]byte("partshash"))) 156 extCommit, err := makeExtCommit(ctx, blockID, height, 1, voteSet, privVals, defaultVoteTime) 157 require.NoError(t, err) 158 commit := extCommit.ToCommit() 159 160 lcae := &LightClientAttackEvidence{ 161 ConflictingBlock: &LightBlock{ 162 SignedHeader: &SignedHeader{ 163 Header: header, 164 Commit: commit, 165 }, 166 ValidatorSet: valSet, 167 }, 168 CommonHeight: commonHeight, 169 TotalVotingPower: valSet.TotalVotingPower(), 170 Timestamp: header.Time, 171 ByzantineValidators: valSet.Validators[:nValidators/2], 172 } 173 assert.NotNil(t, lcae.String()) 174 assert.NotNil(t, lcae.Hash()) 175 assert.Equal(t, lcae.Height(), commonHeight) // Height should be the common Height 176 assert.NotNil(t, lcae.Bytes()) 177 178 // maleate evidence to test hash uniqueness 179 testCases := []struct { 180 testName string 181 malleateEvidence func(*LightClientAttackEvidence) 182 }{ 183 {"Different header", func(ev *LightClientAttackEvidence) { ev.ConflictingBlock.Header = makeHeaderRandom() }}, 184 {"Different common height", func(ev *LightClientAttackEvidence) { 185 ev.CommonHeight = height + 1 186 }}, 187 } 188 189 for _, tc := range testCases { 190 lcae := &LightClientAttackEvidence{ 191 ConflictingBlock: &LightBlock{ 192 SignedHeader: &SignedHeader{ 193 Header: header, 194 Commit: commit, 195 }, 196 ValidatorSet: valSet, 197 }, 198 CommonHeight: commonHeight, 199 TotalVotingPower: valSet.TotalVotingPower(), 200 Timestamp: header.Time, 201 ByzantineValidators: valSet.Validators[:nValidators/2], 202 } 203 hash := lcae.Hash() 204 tc.malleateEvidence(lcae) 205 assert.NotEqual(t, hash, lcae.Hash(), tc.testName) 206 } 207 } 208 209 func TestLightClientAttackEvidenceValidation(t *testing.T) { 210 ctx, cancel := context.WithCancel(context.Background()) 211 defer cancel() 212 213 height := int64(5) 214 commonHeight := height - 1 215 nValidators := 10 216 voteSet, valSet, privVals := randVoteSet(ctx, t, height, 1, tmproto.PrecommitType, nValidators, 1) 217 218 header := makeHeaderRandom() 219 header.Height = height 220 header.ValidatorsHash = valSet.Hash() 221 blockID := makeBlockID(header.Hash(), math.MaxInt32, crypto.Checksum([]byte("partshash"))) 222 extCommit, err := makeExtCommit(ctx, blockID, height, 1, voteSet, privVals, time.Now()) 223 require.NoError(t, err) 224 commit := extCommit.ToCommit() 225 226 lcae := &LightClientAttackEvidence{ 227 ConflictingBlock: &LightBlock{ 228 SignedHeader: &SignedHeader{ 229 Header: header, 230 Commit: commit, 231 }, 232 ValidatorSet: valSet, 233 }, 234 CommonHeight: commonHeight, 235 TotalVotingPower: valSet.TotalVotingPower(), 236 Timestamp: header.Time, 237 ByzantineValidators: valSet.Validators[:nValidators/2], 238 } 239 assert.NoError(t, lcae.ValidateBasic()) 240 241 testCases := []struct { 242 testName string 243 malleateEvidence func(*LightClientAttackEvidence) 244 expectErr bool 245 }{ 246 {"Good LightClientAttackEvidence", func(ev *LightClientAttackEvidence) {}, false}, 247 {"Negative height", func(ev *LightClientAttackEvidence) { ev.CommonHeight = -10 }, true}, 248 {"Height is greater than divergent block", func(ev *LightClientAttackEvidence) { 249 ev.CommonHeight = height + 1 250 }, true}, 251 {"Height is equal to the divergent block", func(ev *LightClientAttackEvidence) { 252 ev.CommonHeight = height 253 }, false}, 254 {"Nil conflicting header", func(ev *LightClientAttackEvidence) { ev.ConflictingBlock.Header = nil }, true}, 255 {"Nil conflicting blocl", func(ev *LightClientAttackEvidence) { ev.ConflictingBlock = nil }, true}, 256 {"Nil validator set", func(ev *LightClientAttackEvidence) { 257 ev.ConflictingBlock.ValidatorSet = &ValidatorSet{} 258 }, true}, 259 {"Negative total voting power", func(ev *LightClientAttackEvidence) { 260 ev.TotalVotingPower = -1 261 }, true}, 262 } 263 for _, tc := range testCases { 264 tc := tc 265 t.Run(tc.testName, func(t *testing.T) { 266 lcae := &LightClientAttackEvidence{ 267 ConflictingBlock: &LightBlock{ 268 SignedHeader: &SignedHeader{ 269 Header: header, 270 Commit: commit, 271 }, 272 ValidatorSet: valSet, 273 }, 274 CommonHeight: commonHeight, 275 TotalVotingPower: valSet.TotalVotingPower(), 276 Timestamp: header.Time, 277 ByzantineValidators: valSet.Validators[:nValidators/2], 278 } 279 tc.malleateEvidence(lcae) 280 if tc.expectErr { 281 assert.Error(t, lcae.ValidateBasic(), tc.testName) 282 } else { 283 assert.NoError(t, lcae.ValidateBasic(), tc.testName) 284 } 285 }) 286 } 287 288 } 289 290 func TestMockEvidenceValidateBasic(t *testing.T) { 291 ctx, cancel := context.WithCancel(context.Background()) 292 defer cancel() 293 294 goodEvidence, err := NewMockDuplicateVoteEvidence(ctx, int64(1), time.Now(), "mock-chain-id") 295 require.NoError(t, err) 296 assert.Nil(t, goodEvidence.ValidateBasic()) 297 } 298 299 func makeVote( 300 ctx context.Context, 301 t *testing.T, 302 val PrivValidator, 303 chainID string, 304 valIndex int32, 305 height int64, 306 round int32, 307 step int, 308 blockID BlockID, 309 time time.Time, 310 ) *Vote { 311 pubKey, err := val.GetPubKey(ctx) 312 require.NoError(t, err) 313 v := &Vote{ 314 ValidatorAddress: pubKey.Address(), 315 ValidatorIndex: valIndex, 316 Height: height, 317 Round: round, 318 Type: tmproto.SignedMsgType(step), 319 BlockID: blockID, 320 Timestamp: time, 321 } 322 323 vpb := v.ToProto() 324 err = val.SignVote(ctx, chainID, vpb) 325 require.NoError(t, err) 326 327 v.Signature = vpb.Signature 328 return v 329 } 330 331 func makeHeaderRandom() *Header { 332 return &Header{ 333 Version: version.Consensus{Block: version.BlockProtocol, App: 1}, 334 ChainID: tmrand.Str(12), 335 Height: int64(mrand.Uint32() + 1), 336 Time: time.Now(), 337 LastBlockID: makeBlockIDRandom(), 338 LastCommitHash: crypto.CRandBytes(crypto.HashSize), 339 DataHash: crypto.CRandBytes(crypto.HashSize), 340 ValidatorsHash: crypto.CRandBytes(crypto.HashSize), 341 NextValidatorsHash: crypto.CRandBytes(crypto.HashSize), 342 ConsensusHash: crypto.CRandBytes(crypto.HashSize), 343 AppHash: crypto.CRandBytes(crypto.HashSize), 344 LastResultsHash: crypto.CRandBytes(crypto.HashSize), 345 EvidenceHash: crypto.CRandBytes(crypto.HashSize), 346 ProposerAddress: crypto.CRandBytes(crypto.AddressSize), 347 } 348 } 349 350 func TestEvidenceProto(t *testing.T) { 351 ctx, cancel := context.WithCancel(context.Background()) 352 defer cancel() 353 354 // -------- Votes -------- 355 val := NewMockPV() 356 blockID := makeBlockID(crypto.Checksum([]byte("blockhash")), math.MaxInt32, crypto.Checksum([]byte("partshash"))) 357 blockID2 := makeBlockID(crypto.Checksum([]byte("blockhash2")), math.MaxInt32, crypto.Checksum([]byte("partshash"))) 358 const chainID = "mychain" 359 v := makeVote(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, 1, 0x01, blockID, defaultVoteTime) 360 v2 := makeVote(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, 2, 0x01, blockID2, defaultVoteTime) 361 362 tests := []struct { 363 testName string 364 evidence Evidence 365 toProtoErr bool 366 fromProtoErr bool 367 }{ 368 {"nil fail", nil, true, true}, 369 {"DuplicateVoteEvidence empty fail", &DuplicateVoteEvidence{}, false, true}, 370 {"DuplicateVoteEvidence nil voteB", &DuplicateVoteEvidence{VoteA: v, VoteB: nil}, false, true}, 371 {"DuplicateVoteEvidence nil voteA", &DuplicateVoteEvidence{VoteA: nil, VoteB: v}, false, true}, 372 {"DuplicateVoteEvidence success", &DuplicateVoteEvidence{VoteA: v2, VoteB: v}, false, false}, 373 } 374 for _, tt := range tests { 375 tt := tt 376 t.Run(tt.testName, func(t *testing.T) { 377 pb, err := EvidenceToProto(tt.evidence) 378 if tt.toProtoErr { 379 assert.Error(t, err, tt.testName) 380 return 381 } 382 assert.NoError(t, err, tt.testName) 383 384 evi, err := EvidenceFromProto(pb) 385 if tt.fromProtoErr { 386 assert.Error(t, err, tt.testName) 387 return 388 } 389 require.Equal(t, tt.evidence, evi, tt.testName) 390 }) 391 } 392 } 393 394 func TestEvidenceVectors(t *testing.T) { 395 ctx, cancel := context.WithCancel(context.Background()) 396 defer cancel() 397 398 // Votes for duplicateEvidence 399 val := NewMockPV() 400 val.PrivKey = ed25519.GenPrivKeyFromSecret([]byte("it's a secret")) // deterministic key 401 blockID := makeBlockID(crypto.Checksum([]byte("blockhash")), math.MaxInt32, crypto.Checksum([]byte("partshash"))) 402 blockID2 := makeBlockID(crypto.Checksum([]byte("blockhash2")), math.MaxInt32, crypto.Checksum([]byte("partshash"))) 403 const chainID = "mychain" 404 v := makeVote(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, 1, 0x01, blockID, defaultVoteTime) 405 v2 := makeVote(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, 2, 0x01, blockID2, defaultVoteTime) 406 407 // Data for LightClientAttackEvidence 408 height := int64(5) 409 commonHeight := height - 1 410 nValidators := 10 411 voteSet, valSet, privVals := deterministicVoteSet(ctx, t, height, 1, tmproto.PrecommitType, 1) 412 header := &Header{ 413 Version: version.Consensus{Block: 1, App: 1}, 414 ChainID: chainID, 415 Height: height, 416 Time: time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC), 417 LastBlockID: BlockID{}, 418 LastCommitHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), 419 DataHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), 420 ValidatorsHash: valSet.Hash(), 421 NextValidatorsHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), 422 ConsensusHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), 423 AppHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), 424 425 LastResultsHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), 426 427 EvidenceHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), 428 ProposerAddress: []byte("2915b7b15f979e48ebc61774bb1d86ba3136b7eb"), 429 } 430 blockID3 := makeBlockID(header.Hash(), math.MaxInt32, crypto.Checksum([]byte("partshash"))) 431 extCommit, err := makeExtCommit(ctx, blockID3, height, 1, voteSet, privVals, defaultVoteTime) 432 require.NoError(t, err) 433 lcae := &LightClientAttackEvidence{ 434 ConflictingBlock: &LightBlock{ 435 SignedHeader: &SignedHeader{ 436 Header: header, 437 Commit: extCommit.ToCommit(), 438 }, 439 ValidatorSet: valSet, 440 }, 441 CommonHeight: commonHeight, 442 TotalVotingPower: valSet.TotalVotingPower(), 443 Timestamp: header.Time, 444 ByzantineValidators: valSet.Validators[:nValidators/2], 445 } 446 // assert.NoError(t, lcae.ValidateBasic()) 447 448 testCases := []struct { 449 testName string 450 evList EvidenceList 451 expBytes string 452 }{ 453 {"duplicateVoteEvidence", 454 EvidenceList{&DuplicateVoteEvidence{VoteA: v2, VoteB: v}}, 455 "a9ce28d13bb31001fc3e5b7927051baf98f86abdbd64377643a304164c826923", 456 }, 457 {"LightClientAttackEvidence", 458 EvidenceList{lcae}, 459 "2f8782163c3905b26e65823ababc977fe54e97b94e60c0360b1e4726b668bb8e", 460 }, 461 {"LightClientAttackEvidence & DuplicateVoteEvidence", 462 EvidenceList{&DuplicateVoteEvidence{VoteA: v2, VoteB: v}, lcae}, 463 "eedb4b47d6dbc9d43f53da8aa50bb826e8d9fc7d897da777c8af6a04aa74163e", 464 }, 465 } 466 467 for _, tc := range testCases { 468 tc := tc 469 hash := tc.evList.Hash() 470 require.Equal(t, tc.expBytes, hex.EncodeToString(hash), tc.testName) 471 } 472 }