github.com/aakash4dev/cometbft@v0.38.2/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/aakash4dev/cometbft/crypto"
    12  	"github.com/aakash4dev/cometbft/crypto/tmhash"
    13  	cmtrand "github.com/aakash4dev/cometbft/libs/rand"
    14  	cmtproto "github.com/aakash4dev/cometbft/proto/tendermint/types"
    15  	cmtversion "github.com/aakash4dev/cometbft/proto/tendermint/version"
    16  	"github.com/aakash4dev/cometbft/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:            MakeVoteNoError(t, val, chainID, 0, 10, 2, 1, blockID, defaultVoteTime),
    37  		VoteB:            MakeVoteNoError(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, err := NewMockDuplicateVoteEvidence(height, time.Now(), "mock-chain-id")
    47  	require.NoError(t, err)
    48  	assert.Equal(t, ev.Hash(), tmhash.Sum(ev.Bytes()))
    49  	assert.NotNil(t, ev.String())
    50  	assert.Equal(t, ev.Height(), height)
    51  }
    52  
    53  func TestDuplicateVoteEvidenceValidation(t *testing.T) {
    54  	val := NewMockPV()
    55  	blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash")))
    56  	blockID2 := makeBlockID(tmhash.Sum([]byte("blockhash2")), math.MaxInt32, tmhash.Sum([]byte("partshash")))
    57  	const chainID = "mychain"
    58  
    59  	testCases := []struct {
    60  		testName         string
    61  		malleateEvidence func(*DuplicateVoteEvidence)
    62  		expectErr        bool
    63  	}{
    64  		{"Good DuplicateVoteEvidence", func(ev *DuplicateVoteEvidence) {}, false},
    65  		{"Nil vote A", func(ev *DuplicateVoteEvidence) { ev.VoteA = nil }, true},
    66  		{"Nil vote B", func(ev *DuplicateVoteEvidence) { ev.VoteB = nil }, true},
    67  		{"Nil votes", func(ev *DuplicateVoteEvidence) {
    68  			ev.VoteA = nil
    69  			ev.VoteB = nil
    70  		}, true},
    71  		{"Invalid vote type", func(ev *DuplicateVoteEvidence) {
    72  			ev.VoteA = MakeVoteNoError(t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0, blockID2, defaultVoteTime)
    73  		}, true},
    74  		{"Invalid vote order", func(ev *DuplicateVoteEvidence) {
    75  			swap := ev.VoteA.Copy()
    76  			ev.VoteA = ev.VoteB.Copy()
    77  			ev.VoteB = swap
    78  		}, true},
    79  	}
    80  	for _, tc := range testCases {
    81  		tc := tc
    82  		t.Run(tc.testName, func(t *testing.T) {
    83  			vote1 := MakeVoteNoError(t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0x02, blockID, defaultVoteTime)
    84  			vote2 := MakeVoteNoError(t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0x02, blockID2, defaultVoteTime)
    85  			valSet := NewValidatorSet([]*Validator{val.ExtractIntoValidator(10)})
    86  			ev, err := NewDuplicateVoteEvidence(vote1, vote2, defaultVoteTime, valSet)
    87  			require.NoError(t, err)
    88  			tc.malleateEvidence(ev)
    89  			assert.Equal(t, tc.expectErr, ev.ValidateBasic() != nil, "Validate Basic had an unexpected result")
    90  		})
    91  	}
    92  }
    93  
    94  func TestLightClientAttackEvidenceBasic(t *testing.T) {
    95  	height := int64(5)
    96  	commonHeight := height - 1
    97  	nValidators := 10
    98  	voteSet, valSet, privVals := randVoteSet(height, 1, cmtproto.PrecommitType, nValidators, 1, false)
    99  	header := makeHeaderRandom()
   100  	header.Height = height
   101  	blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash")))
   102  	extCommit, err := MakeExtCommit(blockID, height, 1, voteSet, privVals, defaultVoteTime, false)
   103  	require.NoError(t, err)
   104  	commit := extCommit.ToCommit()
   105  
   106  	lcae := &LightClientAttackEvidence{
   107  		ConflictingBlock: &LightBlock{
   108  			SignedHeader: &SignedHeader{
   109  				Header: header,
   110  				Commit: commit,
   111  			},
   112  			ValidatorSet: valSet,
   113  		},
   114  		CommonHeight:        commonHeight,
   115  		TotalVotingPower:    valSet.TotalVotingPower(),
   116  		Timestamp:           header.Time,
   117  		ByzantineValidators: valSet.Validators[:nValidators/2],
   118  	}
   119  	assert.NotNil(t, lcae.String())
   120  	assert.NotNil(t, lcae.Hash())
   121  	assert.Equal(t, lcae.Height(), commonHeight) // Height should be the common Height
   122  	assert.NotNil(t, lcae.Bytes())
   123  
   124  	// maleate evidence to test hash uniqueness
   125  	testCases := []struct {
   126  		testName         string
   127  		malleateEvidence func(*LightClientAttackEvidence)
   128  	}{
   129  		{"Different header", func(ev *LightClientAttackEvidence) { ev.ConflictingBlock.Header = makeHeaderRandom() }},
   130  		{"Different common height", func(ev *LightClientAttackEvidence) {
   131  			ev.CommonHeight = height + 1
   132  		}},
   133  	}
   134  
   135  	for _, tc := range testCases {
   136  		lcae := &LightClientAttackEvidence{
   137  			ConflictingBlock: &LightBlock{
   138  				SignedHeader: &SignedHeader{
   139  					Header: header,
   140  					Commit: commit,
   141  				},
   142  				ValidatorSet: valSet,
   143  			},
   144  			CommonHeight:        commonHeight,
   145  			TotalVotingPower:    valSet.TotalVotingPower(),
   146  			Timestamp:           header.Time,
   147  			ByzantineValidators: valSet.Validators[:nValidators/2],
   148  		}
   149  		hash := lcae.Hash()
   150  		tc.malleateEvidence(lcae)
   151  		assert.NotEqual(t, hash, lcae.Hash(), tc.testName)
   152  	}
   153  }
   154  
   155  func TestLightClientAttackEvidenceValidation(t *testing.T) {
   156  	height := int64(5)
   157  	commonHeight := height - 1
   158  	nValidators := 10
   159  	voteSet, valSet, privVals := randVoteSet(height, 1, cmtproto.PrecommitType, nValidators, 1, false)
   160  	header := makeHeaderRandom()
   161  	header.Height = height
   162  	header.ValidatorsHash = valSet.Hash()
   163  	blockID := makeBlockID(header.Hash(), math.MaxInt32, tmhash.Sum([]byte("partshash")))
   164  	extCommit, err := MakeExtCommit(blockID, height, 1, voteSet, privVals, time.Now(), false)
   165  	require.NoError(t, err)
   166  	commit := extCommit.ToCommit()
   167  
   168  	lcae := &LightClientAttackEvidence{
   169  		ConflictingBlock: &LightBlock{
   170  			SignedHeader: &SignedHeader{
   171  				Header: header,
   172  				Commit: commit,
   173  			},
   174  			ValidatorSet: valSet,
   175  		},
   176  		CommonHeight:        commonHeight,
   177  		TotalVotingPower:    valSet.TotalVotingPower(),
   178  		Timestamp:           header.Time,
   179  		ByzantineValidators: valSet.Validators[:nValidators/2],
   180  	}
   181  	assert.NoError(t, lcae.ValidateBasic())
   182  
   183  	testCases := []struct {
   184  		testName         string
   185  		malleateEvidence func(*LightClientAttackEvidence)
   186  		expectErr        bool
   187  	}{
   188  		{"Good LightClientAttackEvidence", func(ev *LightClientAttackEvidence) {}, false},
   189  		{"Negative height", func(ev *LightClientAttackEvidence) { ev.CommonHeight = -10 }, true},
   190  		{"Height is greater than divergent block", func(ev *LightClientAttackEvidence) {
   191  			ev.CommonHeight = height + 1
   192  		}, true},
   193  		{"Height is equal to the divergent block", func(ev *LightClientAttackEvidence) {
   194  			ev.CommonHeight = height
   195  		}, false},
   196  		{"Nil conflicting header", func(ev *LightClientAttackEvidence) { ev.ConflictingBlock.Header = nil }, true},
   197  		{"Nil conflicting blocl", func(ev *LightClientAttackEvidence) { ev.ConflictingBlock = nil }, true},
   198  		{"Nil validator set", func(ev *LightClientAttackEvidence) {
   199  			ev.ConflictingBlock.ValidatorSet = &ValidatorSet{}
   200  		}, true},
   201  		{"Negative total voting power", func(ev *LightClientAttackEvidence) {
   202  			ev.TotalVotingPower = -1
   203  		}, true},
   204  	}
   205  	for _, tc := range testCases {
   206  		tc := tc
   207  		t.Run(tc.testName, func(t *testing.T) {
   208  			lcae := &LightClientAttackEvidence{
   209  				ConflictingBlock: &LightBlock{
   210  					SignedHeader: &SignedHeader{
   211  						Header: header,
   212  						Commit: commit,
   213  					},
   214  					ValidatorSet: valSet,
   215  				},
   216  				CommonHeight:        commonHeight,
   217  				TotalVotingPower:    valSet.TotalVotingPower(),
   218  				Timestamp:           header.Time,
   219  				ByzantineValidators: valSet.Validators[:nValidators/2],
   220  			}
   221  			tc.malleateEvidence(lcae)
   222  			if tc.expectErr {
   223  				assert.Error(t, lcae.ValidateBasic(), tc.testName)
   224  			} else {
   225  				assert.NoError(t, lcae.ValidateBasic(), tc.testName)
   226  			}
   227  		})
   228  	}
   229  
   230  }
   231  
   232  func TestMockEvidenceValidateBasic(t *testing.T) {
   233  	goodEvidence, err := NewMockDuplicateVoteEvidence(int64(1), time.Now(), "mock-chain-id")
   234  	require.NoError(t, err)
   235  	assert.Nil(t, goodEvidence.ValidateBasic())
   236  }
   237  
   238  func makeHeaderRandom() *Header {
   239  	return &Header{
   240  		Version:            cmtversion.Consensus{Block: version.BlockProtocol, App: 1},
   241  		ChainID:            cmtrand.Str(12),
   242  		Height:             int64(cmtrand.Uint16()) + 1,
   243  		Time:               time.Now(),
   244  		LastBlockID:        makeBlockIDRandom(),
   245  		LastCommitHash:     crypto.CRandBytes(tmhash.Size),
   246  		DataHash:           crypto.CRandBytes(tmhash.Size),
   247  		ValidatorsHash:     crypto.CRandBytes(tmhash.Size),
   248  		NextValidatorsHash: crypto.CRandBytes(tmhash.Size),
   249  		ConsensusHash:      crypto.CRandBytes(tmhash.Size),
   250  		AppHash:            crypto.CRandBytes(tmhash.Size),
   251  		LastResultsHash:    crypto.CRandBytes(tmhash.Size),
   252  		EvidenceHash:       crypto.CRandBytes(tmhash.Size),
   253  		ProposerAddress:    crypto.CRandBytes(crypto.AddressSize),
   254  	}
   255  }
   256  
   257  func TestEvidenceProto(t *testing.T) {
   258  	// -------- Votes --------
   259  	val := NewMockPV()
   260  	blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash")))
   261  	blockID2 := makeBlockID(tmhash.Sum([]byte("blockhash2")), math.MaxInt32, tmhash.Sum([]byte("partshash")))
   262  	const chainID = "mychain"
   263  	v := MakeVoteNoError(t, val, chainID, math.MaxInt32, math.MaxInt64, 1, 0x01, blockID, defaultVoteTime)
   264  	v2 := MakeVoteNoError(t, val, chainID, math.MaxInt32, math.MaxInt64, 2, 0x01, blockID2, defaultVoteTime)
   265  
   266  	// -------- SignedHeaders --------
   267  	const height int64 = 37
   268  
   269  	var (
   270  		header1 = makeHeaderRandom()
   271  		header2 = makeHeaderRandom()
   272  	)
   273  
   274  	header1.Height = height
   275  	header1.LastBlockID = blockID
   276  	header1.ChainID = chainID
   277  
   278  	header2.Height = height
   279  	header2.LastBlockID = blockID
   280  	header2.ChainID = chainID
   281  
   282  	tests := []struct {
   283  		testName     string
   284  		evidence     Evidence
   285  		toProtoErr   bool
   286  		fromProtoErr bool
   287  	}{
   288  		{"nil fail", nil, true, true},
   289  		{"DuplicateVoteEvidence empty fail", &DuplicateVoteEvidence{}, false, true},
   290  		{"DuplicateVoteEvidence nil voteB", &DuplicateVoteEvidence{VoteA: v, VoteB: nil}, false, true},
   291  		{"DuplicateVoteEvidence nil voteA", &DuplicateVoteEvidence{VoteA: nil, VoteB: v}, false, true},
   292  		{"DuplicateVoteEvidence success", &DuplicateVoteEvidence{VoteA: v2, VoteB: v}, false, false},
   293  	}
   294  	for _, tt := range tests {
   295  		tt := tt
   296  		t.Run(tt.testName, func(t *testing.T) {
   297  			pb, err := EvidenceToProto(tt.evidence)
   298  			if tt.toProtoErr {
   299  				assert.Error(t, err, tt.testName)
   300  				return
   301  			}
   302  			assert.NoError(t, err, tt.testName)
   303  
   304  			evi, err := EvidenceFromProto(pb)
   305  			if tt.fromProtoErr {
   306  				assert.Error(t, err, tt.testName)
   307  				return
   308  			}
   309  			require.Equal(t, tt.evidence, evi, tt.testName)
   310  		})
   311  	}
   312  }