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  }