github.com/lazyledger/lazyledger-core@v0.35.0-dev.0.20210613111200-4c651f053571/light/detector_test.go (about)

     1  package light_test
     2  
     3  import (
     4  	"testing"
     5  	"time"
     6  
     7  	"github.com/stretchr/testify/assert"
     8  	"github.com/stretchr/testify/require"
     9  
    10  	"github.com/lazyledger/lazyledger-core/libs/db/memdb"
    11  	"github.com/lazyledger/lazyledger-core/libs/log"
    12  	"github.com/lazyledger/lazyledger-core/light"
    13  	"github.com/lazyledger/lazyledger-core/light/provider"
    14  	mockp "github.com/lazyledger/lazyledger-core/light/provider/mock"
    15  	dbs "github.com/lazyledger/lazyledger-core/light/store/db"
    16  	"github.com/lazyledger/lazyledger-core/types"
    17  )
    18  
    19  func TestLightClientAttackEvidence_Lunatic(t *testing.T) {
    20  	// primary performs a lunatic attack
    21  	var (
    22  		latestHeight      = int64(10)
    23  		valSize           = 5
    24  		divergenceHeight  = int64(6)
    25  		primaryHeaders    = make(map[int64]*types.SignedHeader, latestHeight)
    26  		primaryValidators = make(map[int64]*types.ValidatorSet, latestHeight)
    27  	)
    28  
    29  	witnessHeaders, witnessValidators, chainKeys := genMockNodeWithKeys(chainID, latestHeight, valSize, 2, bTime)
    30  	witness := mockp.New(chainID, witnessHeaders, witnessValidators)
    31  	forgedKeys := chainKeys[divergenceHeight-1].ChangeKeys(3) // we change 3 out of the 5 validators (still 2/5 remain)
    32  	forgedVals := forgedKeys.ToValidators(2, 0)
    33  
    34  	for height := int64(1); height <= latestHeight; height++ {
    35  		if height < divergenceHeight {
    36  			primaryHeaders[height] = witnessHeaders[height]
    37  			primaryValidators[height] = witnessValidators[height]
    38  			continue
    39  		}
    40  		primaryHeaders[height] = forgedKeys.GenSignedHeader(chainID, height, bTime.Add(time.Duration(height)*time.Minute),
    41  			nil, forgedVals, forgedVals, hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(forgedKeys))
    42  		primaryValidators[height] = forgedVals
    43  	}
    44  	primary := mockp.New(chainID, primaryHeaders, primaryValidators)
    45  
    46  	c, err := light.NewClient(
    47  		ctx,
    48  		chainID,
    49  		light.TrustOptions{
    50  			Period: 4 * time.Hour,
    51  			Height: 1,
    52  			Hash:   primaryHeaders[1].Hash(),
    53  		},
    54  		primary,
    55  		[]provider.Provider{witness},
    56  		dbs.New(memdb.NewDB(), chainID),
    57  		light.Logger(log.TestingLogger()),
    58  		light.MaxRetryAttempts(1),
    59  	)
    60  	require.NoError(t, err)
    61  
    62  	// Check verification returns an error.
    63  	_, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour))
    64  	if assert.Error(t, err) {
    65  		assert.Equal(t, light.ErrLightClientAttack, err)
    66  	}
    67  
    68  	// Check evidence was sent to both full nodes.
    69  	evAgainstPrimary := &types.LightClientAttackEvidence{
    70  		// after the divergence height the valset doesn't change so we expect the evidence to be for height 10
    71  		ConflictingBlock: &types.LightBlock{
    72  			SignedHeader: primaryHeaders[10],
    73  			ValidatorSet: primaryValidators[10],
    74  		},
    75  		CommonHeight: 4,
    76  	}
    77  	assert.True(t, witness.HasEvidence(evAgainstPrimary))
    78  
    79  	evAgainstWitness := &types.LightClientAttackEvidence{
    80  		// when forming evidence against witness we learn that the canonical chain continued to change validator sets
    81  		// hence the conflicting block is at 7
    82  		ConflictingBlock: &types.LightBlock{
    83  			SignedHeader: witnessHeaders[7],
    84  			ValidatorSet: witnessValidators[7],
    85  		},
    86  		CommonHeight: 4,
    87  	}
    88  	assert.True(t, primary.HasEvidence(evAgainstWitness))
    89  }
    90  
    91  func TestLightClientAttackEvidence_Equivocation(t *testing.T) {
    92  	verificationOptions := map[string]light.Option{
    93  		"sequential": light.SequentialVerification(),
    94  		"skipping":   light.SkippingVerification(light.DefaultTrustLevel),
    95  	}
    96  
    97  	for s, verificationOption := range verificationOptions {
    98  		t.Log("==> verification", s)
    99  
   100  		// primary performs an equivocation attack
   101  		var (
   102  			latestHeight      = int64(10)
   103  			valSize           = 5
   104  			divergenceHeight  = int64(6)
   105  			primaryHeaders    = make(map[int64]*types.SignedHeader, latestHeight)
   106  			primaryValidators = make(map[int64]*types.ValidatorSet, latestHeight)
   107  		)
   108  		// validators don't change in this network (however we still use a map just for convenience)
   109  		witnessHeaders, witnessValidators, chainKeys := genMockNodeWithKeys(chainID, latestHeight+2, valSize, 2, bTime)
   110  		witness := mockp.New(chainID, witnessHeaders, witnessValidators)
   111  
   112  		for height := int64(1); height <= latestHeight; height++ {
   113  			if height < divergenceHeight {
   114  				primaryHeaders[height] = witnessHeaders[height]
   115  				primaryValidators[height] = witnessValidators[height]
   116  				continue
   117  			}
   118  			// we don't have a network partition so we will make 4/5 (greater than 2/3) malicious and vote again for
   119  			// a different block (which we do by adding txs)
   120  			primaryHeaders[height] = chainKeys[height].GenSignedHeader(chainID, height,
   121  				bTime.Add(time.Duration(height)*time.Minute), []types.Tx{[]byte("abcd")},
   122  				witnessValidators[height], witnessValidators[height+1], hash("app_hash"),
   123  				hash("cons_hash"), hash("results_hash"), 0, len(chainKeys[height])-1)
   124  			primaryValidators[height] = witnessValidators[height]
   125  		}
   126  		primary := mockp.New(chainID, primaryHeaders, primaryValidators)
   127  
   128  		c, err := light.NewClient(
   129  			ctx,
   130  			chainID,
   131  			light.TrustOptions{
   132  				Period: 4 * time.Hour,
   133  				Height: 1,
   134  				Hash:   primaryHeaders[1].Hash(),
   135  			},
   136  			primary,
   137  			[]provider.Provider{witness},
   138  			dbs.New(memdb.NewDB(), chainID),
   139  			light.Logger(log.TestingLogger()),
   140  			light.MaxRetryAttempts(1),
   141  			verificationOption,
   142  		)
   143  		require.NoError(t, err)
   144  
   145  		// Check verification returns an error.
   146  		_, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour))
   147  		if assert.Error(t, err) {
   148  			assert.Equal(t, light.ErrLightClientAttack, err)
   149  		}
   150  
   151  		// Check evidence was sent to both full nodes.
   152  		// Common height should be set to the height of the divergent header in the instance
   153  		// of an equivocation attack and the validator sets are the same as what the witness has
   154  		evAgainstPrimary := &types.LightClientAttackEvidence{
   155  			ConflictingBlock: &types.LightBlock{
   156  				SignedHeader: primaryHeaders[divergenceHeight],
   157  				ValidatorSet: primaryValidators[divergenceHeight],
   158  			},
   159  			CommonHeight: divergenceHeight,
   160  		}
   161  		assert.True(t, witness.HasEvidence(evAgainstPrimary))
   162  
   163  		evAgainstWitness := &types.LightClientAttackEvidence{
   164  			ConflictingBlock: &types.LightBlock{
   165  				SignedHeader: witnessHeaders[divergenceHeight],
   166  				ValidatorSet: witnessValidators[divergenceHeight],
   167  			},
   168  			CommonHeight: divergenceHeight,
   169  		}
   170  		assert.True(t, primary.HasEvidence(evAgainstWitness))
   171  	}
   172  }
   173  
   174  // 1. Different nodes therefore a divergent header is produced.
   175  // => light client returns an error upon creation because primary and witness
   176  // have a different view.
   177  func TestClientDivergentTraces1(t *testing.T) {
   178  	primary := mockp.New(genMockNode(chainID, 10, 5, 2, bTime))
   179  	firstBlock, err := primary.LightBlock(ctx, 1)
   180  	require.NoError(t, err)
   181  	witness := mockp.New(genMockNode(chainID, 10, 5, 2, bTime))
   182  
   183  	_, err = light.NewClient(
   184  		ctx,
   185  		chainID,
   186  		light.TrustOptions{
   187  			Height: 1,
   188  			Hash:   firstBlock.Hash(),
   189  			Period: 4 * time.Hour,
   190  		},
   191  		primary,
   192  		[]provider.Provider{witness},
   193  		dbs.New(memdb.NewDB(), chainID),
   194  		light.Logger(log.TestingLogger()),
   195  		light.MaxRetryAttempts(1),
   196  	)
   197  	require.Error(t, err)
   198  	assert.Contains(t, err.Error(), "does not match primary")
   199  }
   200  
   201  // 2. Two out of three nodes don't respond but the third has a header that matches
   202  // => verification should be successful and all the witnesses should remain
   203  func TestClientDivergentTraces2(t *testing.T) {
   204  	primary := mockp.New(genMockNode(chainID, 10, 5, 2, bTime))
   205  	firstBlock, err := primary.LightBlock(ctx, 1)
   206  	require.NoError(t, err)
   207  	c, err := light.NewClient(
   208  		ctx,
   209  		chainID,
   210  		light.TrustOptions{
   211  			Height: 1,
   212  			Hash:   firstBlock.Hash(),
   213  			Period: 4 * time.Hour,
   214  		},
   215  		primary,
   216  		[]provider.Provider{deadNode, deadNode, primary},
   217  		dbs.New(memdb.NewDB(), chainID),
   218  		light.Logger(log.TestingLogger()),
   219  		light.MaxRetryAttempts(1),
   220  	)
   221  	require.NoError(t, err)
   222  
   223  	_, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour))
   224  	assert.NoError(t, err)
   225  	assert.Equal(t, 3, len(c.Witnesses()))
   226  }
   227  
   228  // 3. witness has the same first header, but different second header
   229  // => creation should succeed, but the verification should fail
   230  func TestClientDivergentTraces3(t *testing.T) {
   231  	_, primaryHeaders, primaryVals := genMockNode(chainID, 10, 5, 2, bTime)
   232  	primary := mockp.New(chainID, primaryHeaders, primaryVals)
   233  
   234  	firstBlock, err := primary.LightBlock(ctx, 1)
   235  	require.NoError(t, err)
   236  
   237  	_, mockHeaders, mockVals := genMockNode(chainID, 10, 5, 2, bTime)
   238  	mockHeaders[1] = primaryHeaders[1]
   239  	mockVals[1] = primaryVals[1]
   240  	witness := mockp.New(chainID, mockHeaders, mockVals)
   241  
   242  	c, err := light.NewClient(
   243  		ctx,
   244  		chainID,
   245  		light.TrustOptions{
   246  			Height: 1,
   247  			Hash:   firstBlock.Hash(),
   248  			Period: 4 * time.Hour,
   249  		},
   250  		primary,
   251  		[]provider.Provider{witness},
   252  		dbs.New(memdb.NewDB(), chainID),
   253  		light.Logger(log.TestingLogger()),
   254  		light.MaxRetryAttempts(1),
   255  	)
   256  	require.NoError(t, err)
   257  
   258  	_, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour))
   259  	assert.Error(t, err)
   260  	assert.Equal(t, 0, len(c.Witnesses()))
   261  }