func TestClientDivergentTraces1(t *testing.T) {
	headers, vals, _ := genLightBlocksWithKeys(chainID, 1, 5, 2, bTime)
	mockPrimary := mockNodeFromHeadersAndVals(headers, vals)
	firstBlock, err := mockPrimary.LightBlock(ctx, 1)
	require.NoError(t, err)
	headers, vals, _ = genLightBlocksWithKeys(chainID, 1, 5, 2, bTime)
	mockWitness := mockNodeFromHeadersAndVals(headers, vals)
	_, err = light.NewClient(
		ctx,
		chainID,
		light.TrustOptions{
			Height: 1,
			Hash:   firstBlock.Hash(),
			Period: 4 * time.Hour,
		},
		mockPrimary,
		[]provider.Provider{mockWitness},
		dbs.New(dbm.NewMemDB()),
		light.Logger(log.TestingLogger()),
	)
	require.Error(t, err)
	assert.Contains(t, err.Error(), "does not match primary")
	mockWitness.AssertExpectations(t)
	mockPrimary.AssertExpectations(t)
}
1. Different nodes therefore a divergent header is produced.
=> light client returns an error upon creation because primary and witness
have a different view.
package light_test
import (
	"bytes"
	"testing"
	"time"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
	dbm "github.com/tendermint/tm-db"
	"github.com/tendermint/tendermint/libs/log"
	"github.com/tendermint/tendermint/light"
	"github.com/tendermint/tendermint/light/provider"
	provider_mocks "github.com/tendermint/tendermint/light/provider/mocks"
	dbs "github.com/tendermint/tendermint/light/store/db"
	"github.com/tendermint/tendermint/types"
)
func TestLightClientAttackEvidence_Lunatic(t *testing.T) {
	var (
		latestHeight      = int64(3)
		valSize           = 5
		divergenceHeight  = int64(2)
		primaryHeaders    = make(map[int64]*types.SignedHeader, latestHeight)
		primaryValidators = make(map[int64]*types.ValidatorSet, latestHeight)
	)
	witnessHeaders, witnessValidators, chainKeys := genLightBlocksWithKeys(chainID, latestHeight, valSize, 2, bTime)
	forgedKeys := chainKeys[divergenceHeight-1].ChangeKeys(3)  
	forgedVals := forgedKeys.ToValidators(2, 0)
	for height := int64(1); height <= latestHeight; height++ {
		if height < divergenceHeight {
			primaryHeaders[height] = witnessHeaders[height]
			primaryValidators[height] = witnessValidators[height]
			continue
		}
		primaryHeaders[height] = forgedKeys.GenSignedHeader(chainID, height, bTime.Add(time.Duration(height)*time.Minute),
			nil, forgedVals, forgedVals, hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(forgedKeys))
		primaryValidators[height] = forgedVals
	}
	delete(witnessHeaders, 2)
	delete(primaryHeaders, 2)
	mockWitness := mockNodeFromHeadersAndVals(witnessHeaders, witnessValidators)
	mockPrimary := mockNodeFromHeadersAndVals(primaryHeaders, primaryValidators)
	mockWitness.On("ReportEvidence", mock.Anything, mock.MatchedBy(func(evidence types.Evidence) bool {
		evAgainstPrimary := &types.LightClientAttackEvidence{
			ConflictingBlock: &types.LightBlock{
				SignedHeader: primaryHeaders[latestHeight],
				ValidatorSet: primaryValidators[latestHeight],
			},
			CommonHeight: 1,
		}
		return bytes.Equal(evidence.Hash(), evAgainstPrimary.Hash())
	})).Return(nil)
	mockPrimary.On("ReportEvidence", mock.Anything, mock.MatchedBy(func(evidence types.Evidence) bool {
		evAgainstWitness := &types.LightClientAttackEvidence{
			ConflictingBlock: &types.LightBlock{
				SignedHeader: witnessHeaders[divergenceHeight+1],
				ValidatorSet: witnessValidators[divergenceHeight+1],
			},
			CommonHeight: divergenceHeight - 1,
		}
		return bytes.Equal(evidence.Hash(), evAgainstWitness.Hash())
	})).Return(nil)
	c, err := light.NewClient(
		ctx,
		chainID,
		light.TrustOptions{
			Period: 4 * time.Hour,
			Height: 1,
			Hash:   primaryHeaders[1].Hash(),
		},
		mockPrimary,
		[]provider.Provider{mockWitness},
		dbs.New(dbm.NewMemDB()),
		light.Logger(log.TestingLogger()),
	)
	require.NoError(t, err)
	_, err = c.VerifyLightBlockAtHeight(ctx, latestHeight, bTime.Add(1*time.Hour))
	if assert.Error(t, err) {
		assert.Equal(t, light.ErrLightClientAttack, err)
	}
	mockWitness.AssertExpectations(t)
	mockPrimary.AssertExpectations(t)
}
func TestLightClientAttackEvidence_Equivocation(t *testing.T) {
	cases := []struct {
		name                      string
		lightOption               light.Option
		unusedWitnessBlockHeights []int64
		unusedPrimaryBlockHeights []int64
		latestHeight              int64
		divergenceHeight          int64
	}{
		{
			name:                      "sequential",
			lightOption:               light.SequentialVerification(),
			unusedWitnessBlockHeights: []int64{4, 6},
			latestHeight:              int64(5),
			divergenceHeight:          int64(3),
		},
		{
			name:                      "skipping",
			lightOption:               light.SkippingVerification(light.DefaultTrustLevel),
			unusedWitnessBlockHeights: []int64{2, 4, 6},
			unusedPrimaryBlockHeights: []int64{2, 4, 6},
			latestHeight:              int64(5),
			divergenceHeight:          int64(3),
		},
	}
	for _, tc := range cases {
		testCase := tc
		t.Run(testCase.name, func(t *testing.T) {
			var (
				valSize        = 5
				primaryHeaders = make(map[int64]*types.SignedHeader, testCase.latestHeight)
				primaryValidators = make(map[int64]*types.ValidatorSet, testCase.latestHeight)
			)
			witnessHeaders, witnessValidators, chainKeys := genLightBlocksWithKeys(chainID,
				testCase.latestHeight+1, valSize, 2, bTime)
			for height := int64(1); height <= testCase.latestHeight; height++ {
				if height < testCase.divergenceHeight {
					primaryHeaders[height] = witnessHeaders[height]
					primaryValidators[height] = witnessValidators[height]
					continue
				}
				primaryHeaders[height] = chainKeys[height].GenSignedHeader(chainID, height,
					bTime.Add(time.Duration(height)*time.Minute), []types.Tx{[]byte("abcd")},
					witnessValidators[height], witnessValidators[height+1], hash("app_hash"),
					hash("cons_hash"), hash("results_hash"), 0, len(chainKeys[height])-1)
				primaryValidators[height] = witnessValidators[height]
			}
			for _, height := range testCase.unusedWitnessBlockHeights {
				delete(witnessHeaders, height)
			}
			mockWitness := mockNodeFromHeadersAndVals(witnessHeaders, witnessValidators)
			for _, height := range testCase.unusedPrimaryBlockHeights {
				delete(primaryHeaders, height)
			}
			mockPrimary := mockNodeFromHeadersAndVals(primaryHeaders, primaryValidators)
			mockWitness.On("ReportEvidence", mock.Anything, mock.MatchedBy(func(evidence types.Evidence) bool {
				evAgainstPrimary := &types.LightClientAttackEvidence{
					ConflictingBlock: &types.LightBlock{
						SignedHeader: primaryHeaders[testCase.divergenceHeight],
						ValidatorSet: primaryValidators[testCase.divergenceHeight],
					},
					CommonHeight: testCase.divergenceHeight,
				}
				return bytes.Equal(evidence.Hash(), evAgainstPrimary.Hash())
			})).Return(nil)
			mockPrimary.On("ReportEvidence", mock.Anything, mock.MatchedBy(func(evidence types.Evidence) bool {
				evAgainstWitness := &types.LightClientAttackEvidence{
					ConflictingBlock: &types.LightBlock{
						SignedHeader: witnessHeaders[testCase.divergenceHeight],
						ValidatorSet: witnessValidators[testCase.divergenceHeight],
					},
					CommonHeight: testCase.divergenceHeight,
				}
				return bytes.Equal(evidence.Hash(), evAgainstWitness.Hash())
			})).Return(nil)
			c, err := light.NewClient(
				ctx,
				chainID,
				light.TrustOptions{
					Period: 4 * time.Hour,
					Height: 1,
					Hash:   primaryHeaders[1].Hash(),
				},
				mockPrimary,
				[]provider.Provider{mockWitness},
				dbs.New(dbm.NewMemDB()),
				light.Logger(log.TestingLogger()),
				testCase.lightOption,
			)
			require.NoError(t, err)
			_, err = c.VerifyLightBlockAtHeight(ctx, testCase.latestHeight, bTime.Add(300*time.Second))
			if assert.Error(t, err) {
				assert.Equal(t, light.ErrLightClientAttack, err)
			}
			mockWitness.AssertExpectations(t)
			mockPrimary.AssertExpectations(t)
		})
	}
}
func TestLightClientAttackEvidence_ForwardLunatic(t *testing.T) {
	var (
		latestHeight      = int64(10)
		valSize           = 5
		forgedHeight      = int64(12)
		proofHeight       = int64(11)
		primaryHeaders    = make(map[int64]*types.SignedHeader, forgedHeight)
		primaryValidators = make(map[int64]*types.ValidatorSet, forgedHeight)
	)
	witnessHeaders, witnessValidators, chainKeys := genLightBlocksWithKeys(chainID, latestHeight, valSize, 2, bTime)
	for _, unusedHeader := range []int64{3, 5, 6, 8} {
		delete(witnessHeaders, unusedHeader)
	}
	for h := range witnessHeaders {
		primaryHeaders[h] = witnessHeaders[h]
		primaryValidators[h] = witnessValidators[h]
	}
	for _, unusedHeader := range []int64{3, 5, 6, 8} {
		delete(primaryHeaders, unusedHeader)
	}
	forgedKeys := chainKeys[latestHeight].ChangeKeys(3)  
	primaryValidators[forgedHeight] = forgedKeys.ToValidators(2, 0)
	primaryHeaders[forgedHeight] = forgedKeys.GenSignedHeader(
		chainID,
		forgedHeight,
		bTime.Add(time.Duration(latestHeight+1)*time.Minute),  
		nil,
		primaryValidators[forgedHeight],
		primaryValidators[forgedHeight],
		hash("app_hash"),
		hash("cons_hash"),
		hash("results_hash"),
		0, len(forgedKeys),
	)
	mockPrimary := mockNodeFromHeadersAndVals(primaryHeaders, primaryValidators)
	lastBlock, _ := mockPrimary.LightBlock(ctx, forgedHeight)
	mockPrimary.On("LightBlock", mock.Anything, int64(0)).Return(lastBlock, nil)
	mockPrimary.On("LightBlock", mock.Anything, mock.Anything).Return(nil, provider.ErrLightBlockNotFound)
	mockWitness := mockNodeFromHeadersAndVals(witnessHeaders, witnessValidators)
	lastBlock, _ = mockWitness.LightBlock(ctx, latestHeight)
	mockWitness.On("LightBlock", mock.Anything, int64(0)).Return(lastBlock, nil).Once()
	mockWitness.On("LightBlock", mock.Anything, int64(12)).Return(nil, provider.ErrHeightTooHigh)
	mockWitness.On("ReportEvidence", mock.Anything, mock.MatchedBy(func(evidence types.Evidence) bool {
		evAgainstPrimary := &types.LightClientAttackEvidence{
			ConflictingBlock: &types.LightBlock{
				SignedHeader: primaryHeaders[forgedHeight],
				ValidatorSet: primaryValidators[forgedHeight],
			},
			CommonHeight: latestHeight,
		}
		return bytes.Equal(evidence.Hash(), evAgainstPrimary.Hash())
	})).Return(nil).Twice()
	accomplice := mockPrimary
	c, err := light.NewClient(
		ctx,
		chainID,
		light.TrustOptions{
			Period: 4 * time.Hour,
			Height: 1,
			Hash:   primaryHeaders[1].Hash(),
		},
		mockPrimary,
		[]provider.Provider{mockWitness, accomplice},
		dbs.New(dbm.NewMemDB()),
		light.Logger(log.TestingLogger()),
		light.MaxClockDrift(1*time.Second),
		light.MaxBlockLag(1*time.Second),
	)
	require.NoError(t, err)
	vals := chainKeys[latestHeight].ToValidators(2, 0)
	newLb := &types.LightBlock{
		SignedHeader: chainKeys[latestHeight].GenSignedHeader(
			chainID,
			proofHeight,
			bTime.Add(time.Duration(proofHeight+1)*time.Minute),  
			nil,
			vals,
			vals,
			hash("app_hash"),
			hash("cons_hash"),
			hash("results_hash"),
			0, len(chainKeys),
		),
		ValidatorSet: vals,
	}
	go func() {
		time.Sleep(2 * time.Second)
		mockWitness.On("LightBlock", mock.Anything, int64(0)).Return(newLb, nil)
	}()
	_, err = c.Update(ctx, bTime.Add(time.Duration(forgedHeight)*time.Minute))
	if assert.Error(t, err) {
		assert.Equal(t, light.ErrLightClientAttack, err)
	}
	_, err = c.VerifyLightBlockAtHeight(ctx, forgedHeight, bTime.Add(time.Duration(forgedHeight)*time.Minute))
	if assert.Error(t, err) {
		assert.Equal(t, light.ErrLightClientAttack, err)
	}
	mockLaggingWitness := mockNodeFromHeadersAndVals(witnessHeaders, witnessValidators)
	mockLaggingWitness.On("LightBlock", mock.Anything, int64(12)).Return(nil, provider.ErrHeightTooHigh)
	lastBlock, _ = mockLaggingWitness.LightBlock(ctx, latestHeight)
	mockLaggingWitness.On("LightBlock", mock.Anything, int64(0)).Return(lastBlock, nil)
	c, err = light.NewClient(
		ctx,
		chainID,
		light.TrustOptions{
			Period: 4 * time.Hour,
			Height: 1,
			Hash:   primaryHeaders[1].Hash(),
		},
		mockPrimary,
		[]provider.Provider{mockLaggingWitness, accomplice},
		dbs.New(dbm.NewMemDB()),
		light.Logger(log.TestingLogger()),
		light.MaxClockDrift(1*time.Second),
		light.MaxBlockLag(1*time.Second),
	)
	require.NoError(t, err)
	_, err = c.Update(ctx, bTime.Add(time.Duration(forgedHeight)*time.Minute))
	assert.NoError(t, err)
	mockPrimary.AssertExpectations(t)
	mockWitness.AssertExpectations(t)
}