bitbucket.org/number571/tendermint@v0.8.14/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  	dbm "github.com/tendermint/tm-db"
    11  
    12  	"bitbucket.org/number571/tendermint/libs/log"
    13  	"bitbucket.org/number571/tendermint/light"
    14  	"bitbucket.org/number571/tendermint/light/provider"
    15  	mockp "bitbucket.org/number571/tendermint/light/provider/mock"
    16  	dbs "bitbucket.org/number571/tendermint/light/store/db"
    17  	"bitbucket.org/number571/tendermint/types"
    18  )
    19  
    20  func TestLightClientAttackEvidence_Lunatic(t *testing.T) {
    21  	// primary performs a lunatic attack
    22  	var (
    23  		latestHeight      = int64(10)
    24  		valSize           = 5
    25  		divergenceHeight  = int64(6)
    26  		primaryHeaders    = make(map[int64]*types.SignedHeader, latestHeight)
    27  		primaryValidators = make(map[int64]*types.ValidatorSet, latestHeight)
    28  	)
    29  
    30  	witnessHeaders, witnessValidators, chainKeys := genMockNodeWithKeys(chainID, latestHeight, valSize, 2, bTime)
    31  	witness := mockp.New(chainID, witnessHeaders, witnessValidators)
    32  	forgedKeys := chainKeys[divergenceHeight-1].ChangeKeys(3) // we change 3 out of the 5 validators (still 2/5 remain)
    33  	forgedVals := forgedKeys.ToValidators(2, 0)
    34  
    35  	for height := int64(1); height <= latestHeight; height++ {
    36  		if height < divergenceHeight {
    37  			primaryHeaders[height] = witnessHeaders[height]
    38  			primaryValidators[height] = witnessValidators[height]
    39  			continue
    40  		}
    41  		primaryHeaders[height] = forgedKeys.GenSignedHeader(chainID, height, bTime.Add(time.Duration(height)*time.Minute),
    42  			nil, forgedVals, forgedVals, hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(forgedKeys))
    43  		primaryValidators[height] = forgedVals
    44  	}
    45  	primary := mockp.New(chainID, primaryHeaders, primaryValidators)
    46  
    47  	c, err := light.NewClient(
    48  		ctx,
    49  		chainID,
    50  		light.TrustOptions{
    51  			Period: 4 * time.Hour,
    52  			Height: 1,
    53  			Hash:   primaryHeaders[1].Hash(),
    54  		},
    55  		primary,
    56  		[]provider.Provider{witness},
    57  		dbs.New(dbm.NewMemDB()),
    58  		light.Logger(log.TestingLogger()),
    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(dbm.NewMemDB()),
   139  			light.Logger(log.TestingLogger()),
   140  			verificationOption,
   141  		)
   142  		require.NoError(t, err)
   143  
   144  		// Check verification returns an error.
   145  		_, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour))
   146  		if assert.Error(t, err) {
   147  			assert.Equal(t, light.ErrLightClientAttack, err)
   148  		}
   149  
   150  		// Check evidence was sent to both full nodes.
   151  		// Common height should be set to the height of the divergent header in the instance
   152  		// of an equivocation attack and the validator sets are the same as what the witness has
   153  		evAgainstPrimary := &types.LightClientAttackEvidence{
   154  			ConflictingBlock: &types.LightBlock{
   155  				SignedHeader: primaryHeaders[divergenceHeight],
   156  				ValidatorSet: primaryValidators[divergenceHeight],
   157  			},
   158  			CommonHeight: divergenceHeight,
   159  		}
   160  		assert.True(t, witness.HasEvidence(evAgainstPrimary))
   161  
   162  		evAgainstWitness := &types.LightClientAttackEvidence{
   163  			ConflictingBlock: &types.LightBlock{
   164  				SignedHeader: witnessHeaders[divergenceHeight],
   165  				ValidatorSet: witnessValidators[divergenceHeight],
   166  			},
   167  			CommonHeight: divergenceHeight,
   168  		}
   169  		assert.True(t, primary.HasEvidence(evAgainstWitness))
   170  	}
   171  }
   172  
   173  func TestLightClientAttackEvidence_ForwardLunatic(t *testing.T) {
   174  	// primary performs a lunatic attack but changes the time of the header to
   175  	// something in the future relative to the blockchain
   176  	var (
   177  		latestHeight      = int64(10)
   178  		valSize           = 5
   179  		forgedHeight      = int64(12)
   180  		proofHeight       = int64(11)
   181  		primaryHeaders    = make(map[int64]*types.SignedHeader, forgedHeight)
   182  		primaryValidators = make(map[int64]*types.ValidatorSet, forgedHeight)
   183  	)
   184  
   185  	witnessHeaders, witnessValidators, chainKeys := genMockNodeWithKeys(chainID, latestHeight, valSize, 2, bTime)
   186  
   187  	// primary has the exact same headers except it forges one extra header in the future using keys from 2/5ths of
   188  	// the validators
   189  	for h := range witnessHeaders {
   190  		primaryHeaders[h] = witnessHeaders[h]
   191  		primaryValidators[h] = witnessValidators[h]
   192  	}
   193  	forgedKeys := chainKeys[latestHeight].ChangeKeys(3) // we change 3 out of the 5 validators (still 2/5 remain)
   194  	primaryValidators[forgedHeight] = forgedKeys.ToValidators(2, 0)
   195  	primaryHeaders[forgedHeight] = forgedKeys.GenSignedHeader(
   196  		chainID,
   197  		forgedHeight,
   198  		bTime.Add(time.Duration(latestHeight+1)*time.Minute), // 11 mins
   199  		nil,
   200  		primaryValidators[forgedHeight],
   201  		primaryValidators[forgedHeight],
   202  		hash("app_hash"),
   203  		hash("cons_hash"),
   204  		hash("results_hash"),
   205  		0, len(forgedKeys),
   206  	)
   207  
   208  	witness := mockp.New(chainID, witnessHeaders, witnessValidators)
   209  	primary := mockp.New(chainID, primaryHeaders, primaryValidators)
   210  
   211  	laggingWitness := witness.Copy("laggingWitness")
   212  
   213  	// In order to perform the attack, the primary needs at least one accomplice as a witness to also
   214  	// send the forged block
   215  	accomplice := primary
   216  
   217  	c, err := light.NewClient(
   218  		ctx,
   219  		chainID,
   220  		light.TrustOptions{
   221  			Period: 4 * time.Hour,
   222  			Height: 1,
   223  			Hash:   primaryHeaders[1].Hash(),
   224  		},
   225  		primary,
   226  		[]provider.Provider{witness, accomplice},
   227  		dbs.New(dbm.NewMemDB()),
   228  		light.Logger(log.TestingLogger()),
   229  		light.MaxClockDrift(1*time.Second),
   230  		light.MaxBlockLag(1*time.Second),
   231  	)
   232  	require.NoError(t, err)
   233  
   234  	// two seconds later, the supporting withness should receive the header that can be used
   235  	// to prove that there was an attack
   236  	vals := chainKeys[latestHeight].ToValidators(2, 0)
   237  	newLb := &types.LightBlock{
   238  		SignedHeader: chainKeys[latestHeight].GenSignedHeader(
   239  			chainID,
   240  			proofHeight,
   241  			bTime.Add(time.Duration(proofHeight+1)*time.Minute), // 12 mins
   242  			nil,
   243  			vals,
   244  			vals,
   245  			hash("app_hash"),
   246  			hash("cons_hash"),
   247  			hash("results_hash"),
   248  			0, len(chainKeys),
   249  		),
   250  		ValidatorSet: vals,
   251  	}
   252  	go func() {
   253  		time.Sleep(2 * time.Second)
   254  		witness.AddLightBlock(newLb)
   255  	}()
   256  
   257  	// Now assert that verification returns an error. We craft the light clients time to be a little ahead of the chain
   258  	// to allow a window for the attack to manifest itself.
   259  	_, err = c.Update(ctx, bTime.Add(time.Duration(forgedHeight)*time.Minute))
   260  	if assert.Error(t, err) {
   261  		assert.Equal(t, light.ErrLightClientAttack, err)
   262  	}
   263  
   264  	// Check evidence was sent to the witness against the full node
   265  	evAgainstPrimary := &types.LightClientAttackEvidence{
   266  		ConflictingBlock: &types.LightBlock{
   267  			SignedHeader: primaryHeaders[forgedHeight],
   268  			ValidatorSet: primaryValidators[forgedHeight],
   269  		},
   270  		CommonHeight: latestHeight,
   271  	}
   272  	assert.True(t, witness.HasEvidence(evAgainstPrimary))
   273  
   274  	// We attempt the same call but now the supporting witness has a block which should
   275  	// immediately conflict in time with the primary
   276  	_, err = c.VerifyLightBlockAtHeight(ctx, forgedHeight, bTime.Add(time.Duration(forgedHeight)*time.Minute))
   277  	if assert.Error(t, err) {
   278  		assert.Equal(t, light.ErrLightClientAttack, err)
   279  	}
   280  	assert.True(t, witness.HasEvidence(evAgainstPrimary))
   281  
   282  	// Lastly we test the unfortunate case where the light clients supporting witness doesn't update
   283  	// in enough time
   284  	c, err = light.NewClient(
   285  		ctx,
   286  		chainID,
   287  		light.TrustOptions{
   288  			Period: 4 * time.Hour,
   289  			Height: 1,
   290  			Hash:   primaryHeaders[1].Hash(),
   291  		},
   292  		primary,
   293  		[]provider.Provider{laggingWitness, accomplice},
   294  		dbs.New(dbm.NewMemDB()),
   295  		light.Logger(log.TestingLogger()),
   296  		light.MaxClockDrift(1*time.Second),
   297  		light.MaxBlockLag(1*time.Second),
   298  	)
   299  	require.NoError(t, err)
   300  
   301  	_, err = c.Update(ctx, bTime.Add(time.Duration(forgedHeight)*time.Minute))
   302  	assert.NoError(t, err)
   303  
   304  }
   305  
   306  // 1. Different nodes therefore a divergent header is produced.
   307  // => light client returns an error upon creation because primary and witness
   308  // have a different view.
   309  func TestClientDivergentTraces1(t *testing.T) {
   310  	primary := mockp.New(genMockNode(chainID, 10, 5, 2, bTime))
   311  	firstBlock, err := primary.LightBlock(ctx, 1)
   312  	require.NoError(t, err)
   313  	witness := mockp.New(genMockNode(chainID, 10, 5, 2, bTime))
   314  
   315  	_, err = light.NewClient(
   316  		ctx,
   317  		chainID,
   318  		light.TrustOptions{
   319  			Height: 1,
   320  			Hash:   firstBlock.Hash(),
   321  			Period: 4 * time.Hour,
   322  		},
   323  		primary,
   324  		[]provider.Provider{witness},
   325  		dbs.New(dbm.NewMemDB()),
   326  		light.Logger(log.TestingLogger()),
   327  	)
   328  	require.Error(t, err)
   329  	assert.Contains(t, err.Error(), "does not match primary")
   330  }
   331  
   332  // 2. Two out of three nodes don't respond but the third has a header that matches
   333  // => verification should be successful and all the witnesses should remain
   334  func TestClientDivergentTraces2(t *testing.T) {
   335  	primary := mockp.New(genMockNode(chainID, 10, 5, 2, bTime))
   336  	firstBlock, err := primary.LightBlock(ctx, 1)
   337  	require.NoError(t, err)
   338  	c, err := light.NewClient(
   339  		ctx,
   340  		chainID,
   341  		light.TrustOptions{
   342  			Height: 1,
   343  			Hash:   firstBlock.Hash(),
   344  			Period: 4 * time.Hour,
   345  		},
   346  		primary,
   347  		[]provider.Provider{deadNode, deadNode, primary},
   348  		dbs.New(dbm.NewMemDB()),
   349  		light.Logger(log.TestingLogger()),
   350  	)
   351  	require.NoError(t, err)
   352  
   353  	_, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour))
   354  	assert.NoError(t, err)
   355  	assert.Equal(t, 3, len(c.Witnesses()))
   356  }
   357  
   358  // 3. witness has the same first header, but different second header
   359  // => creation should succeed, but the verification should fail
   360  func TestClientDivergentTraces3(t *testing.T) {
   361  	_, primaryHeaders, primaryVals := genMockNode(chainID, 10, 5, 2, bTime)
   362  	primary := mockp.New(chainID, primaryHeaders, primaryVals)
   363  
   364  	firstBlock, err := primary.LightBlock(ctx, 1)
   365  	require.NoError(t, err)
   366  
   367  	_, mockHeaders, mockVals := genMockNode(chainID, 10, 5, 2, bTime)
   368  	mockHeaders[1] = primaryHeaders[1]
   369  	mockVals[1] = primaryVals[1]
   370  	witness := mockp.New(chainID, mockHeaders, mockVals)
   371  
   372  	c, err := light.NewClient(
   373  		ctx,
   374  		chainID,
   375  		light.TrustOptions{
   376  			Height: 1,
   377  			Hash:   firstBlock.Hash(),
   378  			Period: 4 * time.Hour,
   379  		},
   380  		primary,
   381  		[]provider.Provider{witness},
   382  		dbs.New(dbm.NewMemDB()),
   383  		light.Logger(log.TestingLogger()),
   384  	)
   385  	require.NoError(t, err)
   386  
   387  	_, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour))
   388  	assert.Error(t, err)
   389  	assert.Equal(t, 1, len(c.Witnesses()))
   390  }
   391  
   392  // 4. Witness has a divergent header but can not produce a valid trace to back it up.
   393  // It should be ignored
   394  func TestClientDivergentTraces4(t *testing.T) {
   395  	_, primaryHeaders, primaryVals := genMockNode(chainID, 10, 5, 2, bTime)
   396  	primary := mockp.New(chainID, primaryHeaders, primaryVals)
   397  
   398  	firstBlock, err := primary.LightBlock(ctx, 1)
   399  	require.NoError(t, err)
   400  
   401  	_, mockHeaders, mockVals := genMockNode(chainID, 10, 5, 2, bTime)
   402  	witness := primary.Copy("witness")
   403  	witness.AddLightBlock(&types.LightBlock{
   404  		SignedHeader: mockHeaders[10],
   405  		ValidatorSet: mockVals[10],
   406  	})
   407  
   408  	c, err := light.NewClient(
   409  		ctx,
   410  		chainID,
   411  		light.TrustOptions{
   412  			Height: 1,
   413  			Hash:   firstBlock.Hash(),
   414  			Period: 4 * time.Hour,
   415  		},
   416  		primary,
   417  		[]provider.Provider{witness},
   418  		dbs.New(dbm.NewMemDB()),
   419  		light.Logger(log.TestingLogger()),
   420  	)
   421  	require.NoError(t, err)
   422  
   423  	_, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour))
   424  	assert.Error(t, err)
   425  	assert.Equal(t, 1, len(c.Witnesses()))
   426  }