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