github.com/Finschia/ostracon@v1.1.5/types/evidence_test.go (about) 1 package types 2 3 import ( 4 "math" 5 "testing" 6 "time" 7 8 "github.com/stretchr/testify/assert" 9 "github.com/stretchr/testify/require" 10 11 tmproto "github.com/tendermint/tendermint/proto/tendermint/types" 12 tmversion "github.com/tendermint/tendermint/proto/tendermint/version" 13 14 "github.com/Finschia/ostracon/crypto" 15 "github.com/Finschia/ostracon/crypto/tmhash" 16 tmrand "github.com/Finschia/ostracon/libs/rand" 17 "github.com/Finschia/ostracon/version" 18 ) 19 20 var defaultVoteTime = time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC) 21 22 func TestEvidenceList(t *testing.T) { 23 ev := randomDuplicatedVoteEvidence(t) 24 evl := EvidenceList([]Evidence{ev}) 25 26 assert.NotNil(t, evl.Hash()) 27 assert.True(t, evl.Has(ev)) 28 assert.False(t, evl.Has(&DuplicateVoteEvidence{})) 29 } 30 31 func TestMaxEvidenceBytes(t *testing.T) { 32 // time is varint encoded so need to pick the max. 33 // year int, month Month, day, hour, min, sec, nsec int, loc *Location 34 timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC) 35 36 val := NewMockPV() 37 randomPartHash := tmrand.Bytes(int(tmrand.Uint32() % 1000)) 38 randomHash1 := tmrand.Bytes(int(tmrand.Uint32() % 1000)) 39 randomHash2 := tmrand.Bytes(int(tmrand.Uint32() % 1000)) 40 blockID := makeBlockID(tmhash.Sum(randomHash1), math.MaxUint32, tmhash.Sum(randomPartHash)) 41 blockID2 := makeBlockID(tmhash.Sum(randomHash2), math.MaxUint32, tmhash.Sum(randomPartHash)) 42 const chainID = "mychain" 43 ev := &DuplicateVoteEvidence{ 44 VoteA: makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 32, blockID, timestamp), 45 VoteB: makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 32, blockID2, timestamp), 46 TotalVotingPower: math.MaxInt64, 47 ValidatorPower: math.MaxInt64, 48 Timestamp: timestamp, 49 } 50 51 bz, err := ev.ToProto().Marshal() 52 require.NoError(t, err) 53 assert.EqualValues(t, MaxEvidenceBytes(ev), len(bz)) 54 } 55 56 func randomDuplicatedVoteEvidence(t *testing.T) *DuplicateVoteEvidence { 57 val := NewMockPV() 58 blockID := makeBlockID([]byte("blockhash"), 1000, []byte("partshash")) 59 blockID2 := makeBlockID([]byte("blockhash2"), 1000, []byte("partshash")) 60 const chainID = "mychain" 61 return &DuplicateVoteEvidence{ 62 VoteA: makeVote(t, val, chainID, 0, 10, 2, 1, blockID, defaultVoteTime), 63 VoteB: makeVote(t, val, chainID, 0, 10, 2, 1, blockID2, defaultVoteTime.Add(1*time.Minute)), 64 TotalVotingPower: 30, 65 ValidatorPower: 10, 66 Timestamp: defaultVoteTime, 67 } 68 } 69 70 func TestDuplicateVoteEvidence(t *testing.T) { 71 const height = int64(13) 72 ev := NewMockDuplicateVoteEvidence(height, time.Now(), "mock-chain-id") 73 assert.Equal(t, ev.Hash(), tmhash.Sum(ev.Bytes())) 74 assert.NotNil(t, ev.String()) 75 assert.Equal(t, ev.Height(), height) 76 77 ev = randomDuplicatedVoteEvidence(t) 78 assert.Equal(t, ev.Hash(), tmhash.Sum(ev.Bytes())) 79 assert.NotNil(t, ev.String()) 80 } 81 82 func TestDuplicateVoteEvidenceValidation(t *testing.T) { 83 val := NewMockPV() 84 blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) 85 blockID2 := makeBlockID(tmhash.Sum([]byte("blockhash2")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) 86 const chainID = "mychain" 87 88 testCases := []struct { 89 testName string 90 malleateEvidence func(*DuplicateVoteEvidence) 91 expectErr bool 92 }{ 93 {"Good DuplicateVoteEvidence", func(ev *DuplicateVoteEvidence) {}, false}, 94 {"Nil vote A", func(ev *DuplicateVoteEvidence) { ev.VoteA = nil }, true}, 95 {"Nil vote B", func(ev *DuplicateVoteEvidence) { ev.VoteB = nil }, true}, 96 {"Nil votes", func(ev *DuplicateVoteEvidence) { 97 ev.VoteA = nil 98 ev.VoteB = nil 99 }, true}, 100 {"Invalid vote type", func(ev *DuplicateVoteEvidence) { 101 ev.VoteA = makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0, blockID2, defaultVoteTime) 102 }, true}, 103 {"Invalid vote order", func(ev *DuplicateVoteEvidence) { 104 swap := ev.VoteA.Copy() 105 ev.VoteA = ev.VoteB.Copy() 106 ev.VoteB = swap 107 }, true}, 108 } 109 for _, tc := range testCases { 110 tc := tc 111 t.Run(tc.testName, func(t *testing.T) { 112 vote1 := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0x02, blockID, defaultVoteTime) 113 vote2 := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0x02, blockID2, defaultVoteTime) 114 valSet := NewValidatorSet([]*Validator{val.ExtractIntoValidator(10)}) 115 ev := NewDuplicateVoteEvidence(vote1, vote2, defaultVoteTime, valSet) 116 tc.malleateEvidence(ev) 117 assert.Equal(t, tc.expectErr, ev.ValidateBasic() != nil, "Validate Basic had an unexpected result") 118 }) 119 } 120 } 121 122 func TestLightClientAttackEvidenceBasic(t *testing.T) { 123 height := int64(5) 124 commonHeight := height - 1 125 nValidators := 10 126 voteSet, valSet, privVals := randVoteSet(height, 1, tmproto.PrecommitType, nValidators, 1) 127 header := makeHeaderRandom() 128 header.Height = height 129 blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) 130 commit, err := MakeCommit(blockID, height, 1, voteSet, privVals, defaultVoteTime) 131 require.NoError(t, err) 132 lcae := &LightClientAttackEvidence{ 133 ConflictingBlock: &LightBlock{ 134 SignedHeader: &SignedHeader{ 135 Header: header, 136 Commit: commit, 137 }, 138 ValidatorSet: valSet, 139 }, 140 CommonHeight: commonHeight, 141 TotalVotingPower: valSet.TotalVotingPower(), 142 Timestamp: header.Time, 143 ByzantineValidators: valSet.Validators[:nValidators/2], 144 } 145 assert.NotNil(t, lcae.String()) 146 assert.NotNil(t, lcae.Hash()) 147 assert.Equal(t, lcae.Height(), commonHeight) // Height should be the common Height 148 assert.NotNil(t, lcae.Bytes()) 149 150 // maleate evidence to test hash uniqueness 151 testCases := []struct { 152 testName string 153 malleateEvidence func(*LightClientAttackEvidence) 154 }{ 155 {"Different header", func(ev *LightClientAttackEvidence) { ev.ConflictingBlock.Header = makeHeaderRandom() }}, 156 {"Different common height", func(ev *LightClientAttackEvidence) { 157 ev.CommonHeight = height + 1 158 }}, 159 } 160 161 for _, tc := range testCases { 162 lcae := &LightClientAttackEvidence{ 163 ConflictingBlock: &LightBlock{ 164 SignedHeader: &SignedHeader{ 165 Header: header, 166 Commit: commit, 167 }, 168 ValidatorSet: valSet, 169 }, 170 CommonHeight: commonHeight, 171 TotalVotingPower: valSet.TotalVotingPower(), 172 Timestamp: header.Time, 173 ByzantineValidators: valSet.Validators[:nValidators/2], 174 } 175 hash := lcae.Hash() 176 tc.malleateEvidence(lcae) 177 assert.NotEqual(t, hash, lcae.Hash(), tc.testName) 178 } 179 } 180 181 func TestLightClientAttackEvidenceValidation(t *testing.T) { 182 height := int64(5) 183 commonHeight := height - 1 184 nValidators := 10 185 voteSet, valSet, privVals := randVoteSet(height, 1, tmproto.PrecommitType, nValidators, 1) 186 header := makeHeaderRandom() 187 header.Height = height 188 header.ValidatorsHash = valSet.Hash() 189 blockID := makeBlockID(header.Hash(), math.MaxInt32, tmhash.Sum([]byte("partshash"))) 190 commit, err := MakeCommit(blockID, height, 1, voteSet, privVals, time.Now()) 191 require.NoError(t, err) 192 lcae := &LightClientAttackEvidence{ 193 ConflictingBlock: &LightBlock{ 194 SignedHeader: &SignedHeader{ 195 Header: header, 196 Commit: commit, 197 }, 198 ValidatorSet: valSet, 199 }, 200 CommonHeight: commonHeight, 201 TotalVotingPower: valSet.TotalVotingPower(), 202 Timestamp: header.Time, 203 ByzantineValidators: valSet.Validators[:nValidators/2], 204 } 205 assert.NoError(t, lcae.ValidateBasic()) 206 207 testCases := []struct { 208 testName string 209 malleateEvidence func(*LightClientAttackEvidence) 210 expectErr bool 211 }{ 212 {"Good LightClientAttackEvidence", func(ev *LightClientAttackEvidence) {}, false}, 213 {"Negative height", func(ev *LightClientAttackEvidence) { ev.CommonHeight = -10 }, true}, 214 {"Height is greater than divergent block", func(ev *LightClientAttackEvidence) { 215 ev.CommonHeight = height + 1 216 }, true}, 217 {"Height is equal to the divergent block", func(ev *LightClientAttackEvidence) { 218 ev.CommonHeight = height 219 }, false}, 220 {"Nil conflicting header", func(ev *LightClientAttackEvidence) { ev.ConflictingBlock.Header = nil }, true}, 221 {"Nil conflicting blocl", func(ev *LightClientAttackEvidence) { ev.ConflictingBlock = nil }, true}, 222 {"Nil validator set", func(ev *LightClientAttackEvidence) { 223 ev.ConflictingBlock.ValidatorSet = &ValidatorSet{} 224 }, true}, 225 {"Negative total voting power", func(ev *LightClientAttackEvidence) { 226 ev.TotalVotingPower = -1 227 }, true}, 228 } 229 for _, tc := range testCases { 230 tc := tc 231 t.Run(tc.testName, func(t *testing.T) { 232 lcae := &LightClientAttackEvidence{ 233 ConflictingBlock: &LightBlock{ 234 SignedHeader: &SignedHeader{ 235 Header: header, 236 Commit: commit, 237 }, 238 ValidatorSet: valSet, 239 }, 240 CommonHeight: commonHeight, 241 TotalVotingPower: valSet.TotalVotingPower(), 242 Timestamp: header.Time, 243 ByzantineValidators: valSet.Validators[:nValidators/2], 244 } 245 tc.malleateEvidence(lcae) 246 if tc.expectErr { 247 assert.Error(t, lcae.ValidateBasic(), tc.testName) 248 } else { 249 assert.NoError(t, lcae.ValidateBasic(), tc.testName) 250 } 251 }) 252 } 253 254 } 255 256 func TestMockEvidenceValidateBasic(t *testing.T) { 257 goodEvidence := NewMockDuplicateVoteEvidence(int64(1), time.Now(), "mock-chain-id") 258 assert.Nil(t, goodEvidence.ValidateBasic()) 259 } 260 261 func makeVote( 262 t *testing.T, val PrivValidator, chainID string, valIndex int32, height int64, round int32, step int, blockID BlockID, 263 time time.Time) *Vote { 264 pubKey, err := val.GetPubKey() 265 require.NoError(t, err) 266 v := &Vote{ 267 ValidatorAddress: pubKey.Address(), 268 ValidatorIndex: valIndex, 269 Height: height, 270 Round: round, 271 Type: tmproto.SignedMsgType(step), 272 BlockID: blockID, 273 Timestamp: time, 274 Signature: []byte{}, 275 } 276 277 vpb := v.ToProto() 278 err = val.SignVote(chainID, vpb) 279 if err != nil { 280 panic(err) 281 } 282 v.Signature = vpb.Signature 283 return v 284 } 285 286 func makeHeaderRandom() *Header { 287 return &Header{ 288 Version: tmversion.Consensus{Block: version.BlockProtocol, App: version.AppProtocol}, 289 ChainID: tmrand.Str(12), 290 Height: int64(tmrand.Uint16()) + 1, 291 Time: time.Now(), 292 LastBlockID: makeBlockIDRandom(), 293 LastCommitHash: crypto.CRandBytes(tmhash.Size), 294 DataHash: crypto.CRandBytes(tmhash.Size), 295 ValidatorsHash: crypto.CRandBytes(tmhash.Size), 296 NextValidatorsHash: crypto.CRandBytes(tmhash.Size), 297 ConsensusHash: crypto.CRandBytes(tmhash.Size), 298 AppHash: crypto.CRandBytes(tmhash.Size), 299 LastResultsHash: crypto.CRandBytes(tmhash.Size), 300 EvidenceHash: crypto.CRandBytes(tmhash.Size), 301 ProposerAddress: crypto.CRandBytes(crypto.AddressSize), 302 } 303 } 304 305 func TestEvidenceProto(t *testing.T) { 306 // -------- Votes -------- 307 val := NewMockPV() 308 blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) 309 blockID2 := makeBlockID(tmhash.Sum([]byte("blockhash2")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) 310 const chainID = "mychain" 311 v := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, 1, 0x01, blockID, defaultVoteTime) 312 v2 := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, 2, 0x01, blockID2, defaultVoteTime) 313 314 // -------- SignedHeaders -------- 315 const height int64 = 37 316 317 var ( 318 header1 = makeHeaderRandom() 319 header2 = makeHeaderRandom() 320 ) 321 322 header1.Height = height 323 header1.LastBlockID = blockID 324 header1.ChainID = chainID 325 326 header2.Height = height 327 header2.LastBlockID = blockID 328 header2.ChainID = chainID 329 330 tests := []struct { 331 testName string 332 evidence Evidence 333 toProtoErr bool 334 fromProtoErr bool 335 }{ 336 {"nil fail", nil, true, true}, 337 {"DuplicateVoteEvidence empty fail", &DuplicateVoteEvidence{}, false, true}, 338 {"DuplicateVoteEvidence nil voteB", &DuplicateVoteEvidence{VoteA: v, VoteB: nil}, false, true}, 339 {"DuplicateVoteEvidence nil voteA", &DuplicateVoteEvidence{VoteA: nil, VoteB: v}, false, true}, 340 {"DuplicateVoteEvidence success", &DuplicateVoteEvidence{VoteA: v2, VoteB: v}, false, false}, 341 } 342 for _, tt := range tests { 343 tt := tt 344 t.Run(tt.testName, func(t *testing.T) { 345 pb, err := EvidenceToProto(tt.evidence) 346 if tt.toProtoErr { 347 assert.Error(t, err, tt.testName) 348 return 349 } 350 assert.NoError(t, err, tt.testName) 351 352 evi, err := EvidenceFromProto(pb) 353 if tt.fromProtoErr { 354 assert.Error(t, err, tt.testName) 355 return 356 } 357 require.Equal(t, tt.evidence, evi, tt.testName) 358 }) 359 } 360 }