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

     1  package light_test
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/lazyledger/lazyledger-core/libs/db/memdb"
    13  	"github.com/lazyledger/lazyledger-core/libs/log"
    14  	"github.com/lazyledger/lazyledger-core/light"
    15  	"github.com/lazyledger/lazyledger-core/light/provider"
    16  	mockp "github.com/lazyledger/lazyledger-core/light/provider/mock"
    17  	dbs "github.com/lazyledger/lazyledger-core/light/store/db"
    18  	"github.com/lazyledger/lazyledger-core/types"
    19  )
    20  
    21  const (
    22  	chainID = "test"
    23  )
    24  
    25  var (
    26  	ctx      = context.Background()
    27  	keys     = genPrivKeys(4)
    28  	vals     = keys.ToValidators(20, 10)
    29  	bTime, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
    30  	h1       = keys.GenSignedHeader(chainID, 1, bTime, nil, vals, vals,
    31  		hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys))
    32  	// 3/3 signed
    33  	h2 = keys.GenSignedHeaderLastBlockID(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals,
    34  		hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys), types.BlockID{Hash: h1.Hash()})
    35  	// 3/3 signed
    36  	h3 = keys.GenSignedHeaderLastBlockID(chainID, 3, bTime.Add(1*time.Hour), nil, vals, vals,
    37  		hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys), types.BlockID{Hash: h2.Hash()})
    38  	trustPeriod  = 4 * time.Hour
    39  	trustOptions = light.TrustOptions{
    40  		Period: 4 * time.Hour,
    41  		Height: 1,
    42  		Hash:   h1.Hash(),
    43  	}
    44  	valSet = map[int64]*types.ValidatorSet{
    45  		1: vals,
    46  		2: vals,
    47  		3: vals,
    48  		4: vals,
    49  	}
    50  	headerSet = map[int64]*types.SignedHeader{
    51  		1: h1,
    52  		// interim header (3/3 signed)
    53  		2: h2,
    54  		// last header (3/3 signed)
    55  		3: h3,
    56  	}
    57  	l1       = &types.LightBlock{SignedHeader: h1, ValidatorSet: vals}
    58  	l2       = &types.LightBlock{SignedHeader: h2, ValidatorSet: vals}
    59  	fullNode = mockp.New(
    60  		chainID,
    61  		headerSet,
    62  		valSet,
    63  	)
    64  	deadNode      = mockp.NewDeadMock(chainID)
    65  	largeFullNode = mockp.New(genMockNode(chainID, 10, 3, 0, bTime))
    66  )
    67  
    68  func TestValidateTrustOptions(t *testing.T) {
    69  	testCases := []struct {
    70  		err bool
    71  		to  light.TrustOptions
    72  	}{
    73  		{
    74  			false,
    75  			trustOptions,
    76  		},
    77  		{
    78  			true,
    79  			light.TrustOptions{
    80  				Period: -1 * time.Hour,
    81  				Height: 1,
    82  				Hash:   h1.Hash(),
    83  			},
    84  		},
    85  		{
    86  			true,
    87  			light.TrustOptions{
    88  				Period: 1 * time.Hour,
    89  				Height: 0,
    90  				Hash:   h1.Hash(),
    91  			},
    92  		},
    93  		{
    94  			true,
    95  			light.TrustOptions{
    96  				Period: 1 * time.Hour,
    97  				Height: 1,
    98  				Hash:   []byte("incorrect hash"),
    99  			},
   100  		},
   101  	}
   102  
   103  	for _, tc := range testCases {
   104  		err := tc.to.ValidateBasic()
   105  		if tc.err {
   106  			assert.Error(t, err)
   107  		} else {
   108  			assert.NoError(t, err)
   109  		}
   110  	}
   111  
   112  }
   113  
   114  func TestMock(t *testing.T) {
   115  	l, _ := fullNode.LightBlock(ctx, 3)
   116  	assert.Equal(t, int64(3), l.Height)
   117  }
   118  
   119  func TestClient_SequentialVerification(t *testing.T) {
   120  	newKeys := genPrivKeys(4)
   121  	newVals := newKeys.ToValidators(10, 1)
   122  	differentVals, _ := types.RandValidatorSet(10, 100)
   123  
   124  	testCases := []struct {
   125  		name         string
   126  		otherHeaders map[int64]*types.SignedHeader // all except ^
   127  		vals         map[int64]*types.ValidatorSet
   128  		initErr      bool
   129  		verifyErr    bool
   130  	}{
   131  		{
   132  			"good",
   133  			headerSet,
   134  			valSet,
   135  			false,
   136  			false,
   137  		},
   138  		{
   139  			"bad: different first header",
   140  			map[int64]*types.SignedHeader{
   141  				// different header
   142  				1: keys.GenSignedHeader(chainID, 1, bTime.Add(1*time.Hour), nil, vals, vals,
   143  					hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys)),
   144  			},
   145  			map[int64]*types.ValidatorSet{
   146  				1: vals,
   147  			},
   148  			true,
   149  			false,
   150  		},
   151  		{
   152  			"bad: no first signed header",
   153  			map[int64]*types.SignedHeader{},
   154  			map[int64]*types.ValidatorSet{
   155  				1: differentVals,
   156  			},
   157  			true,
   158  			true,
   159  		},
   160  		{
   161  			"bad: different first validator set",
   162  			map[int64]*types.SignedHeader{
   163  				1: h1,
   164  			},
   165  			map[int64]*types.ValidatorSet{
   166  				1: differentVals,
   167  			},
   168  			true,
   169  			true,
   170  		},
   171  		{
   172  			"bad: 1/3 signed interim header",
   173  			map[int64]*types.SignedHeader{
   174  				// trusted header
   175  				1: h1,
   176  				// interim header (1/3 signed)
   177  				2: keys.GenSignedHeader(chainID, 2, bTime.Add(1*time.Hour), nil, vals, vals,
   178  					hash("app_hash"), hash("cons_hash"), hash("results_hash"), len(keys)-1, len(keys)),
   179  				// last header (3/3 signed)
   180  				3: keys.GenSignedHeader(chainID, 3, bTime.Add(2*time.Hour), nil, vals, vals,
   181  					hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys)),
   182  			},
   183  			valSet,
   184  			false,
   185  			true,
   186  		},
   187  		{
   188  			"bad: 1/3 signed last header",
   189  			map[int64]*types.SignedHeader{
   190  				// trusted header
   191  				1: h1,
   192  				// interim header (3/3 signed)
   193  				2: keys.GenSignedHeader(chainID, 2, bTime.Add(1*time.Hour), nil, vals, vals,
   194  					hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys)),
   195  				// last header (1/3 signed)
   196  				3: keys.GenSignedHeader(chainID, 3, bTime.Add(2*time.Hour), nil, vals, vals,
   197  					hash("app_hash"), hash("cons_hash"), hash("results_hash"), len(keys)-1, len(keys)),
   198  			},
   199  			valSet,
   200  			false,
   201  			true,
   202  		},
   203  		{
   204  			"bad: different validator set at height 3",
   205  			headerSet,
   206  			map[int64]*types.ValidatorSet{
   207  				1: vals,
   208  				2: vals,
   209  				3: newVals,
   210  			},
   211  			false,
   212  			true,
   213  		},
   214  	}
   215  
   216  	for _, tc := range testCases {
   217  		tc := tc
   218  		t.Run(tc.name, func(t *testing.T) {
   219  			c, err := light.NewClient(
   220  				ctx,
   221  				chainID,
   222  				trustOptions,
   223  				mockp.New(
   224  					chainID,
   225  					tc.otherHeaders,
   226  					tc.vals,
   227  				),
   228  				[]provider.Provider{mockp.New(
   229  					chainID,
   230  					tc.otherHeaders,
   231  					tc.vals,
   232  				)},
   233  				dbs.New(memdb.NewDB(), chainID),
   234  				light.SequentialVerification(),
   235  				light.Logger(log.TestingLogger()),
   236  			)
   237  
   238  			if tc.initErr {
   239  				require.Error(t, err)
   240  				return
   241  			}
   242  
   243  			require.NoError(t, err)
   244  
   245  			_, err = c.VerifyLightBlockAtHeight(ctx, 3, bTime.Add(3*time.Hour))
   246  			if tc.verifyErr {
   247  				assert.Error(t, err)
   248  			} else {
   249  				assert.NoError(t, err)
   250  			}
   251  		})
   252  	}
   253  }
   254  
   255  func TestClient_SkippingVerification(t *testing.T) {
   256  	// required for 2nd test case
   257  	newKeys := genPrivKeys(4)
   258  	newVals := newKeys.ToValidators(10, 1)
   259  
   260  	// 1/3+ of vals, 2/3- of newVals
   261  	transitKeys := keys.Extend(3)
   262  	transitVals := transitKeys.ToValidators(10, 1)
   263  
   264  	testCases := []struct {
   265  		name         string
   266  		otherHeaders map[int64]*types.SignedHeader // all except ^
   267  		vals         map[int64]*types.ValidatorSet
   268  		initErr      bool
   269  		verifyErr    bool
   270  	}{
   271  		{
   272  			"good",
   273  			map[int64]*types.SignedHeader{
   274  				// trusted header
   275  				1: h1,
   276  				// last header (3/3 signed)
   277  				3: h3,
   278  			},
   279  			valSet,
   280  			false,
   281  			false,
   282  		},
   283  		{
   284  			"good, but val set changes by 2/3 (1/3 of vals is still present)",
   285  			map[int64]*types.SignedHeader{
   286  				// trusted header
   287  				1: h1,
   288  				3: transitKeys.GenSignedHeader(chainID, 3, bTime.Add(2*time.Hour), nil, transitVals, transitVals,
   289  					hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(transitKeys)),
   290  			},
   291  			map[int64]*types.ValidatorSet{
   292  				1: vals,
   293  				2: vals,
   294  				3: transitVals,
   295  			},
   296  			false,
   297  			false,
   298  		},
   299  		{
   300  			"good, but val set changes 100% at height 2",
   301  			map[int64]*types.SignedHeader{
   302  				// trusted header
   303  				1: h1,
   304  				// interim header (3/3 signed)
   305  				2: keys.GenSignedHeader(chainID, 2, bTime.Add(1*time.Hour), nil, vals, newVals,
   306  					hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys)),
   307  				// last header (0/4 of the original val set signed)
   308  				3: newKeys.GenSignedHeader(chainID, 3, bTime.Add(2*time.Hour), nil, newVals, newVals,
   309  					hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(newKeys)),
   310  			},
   311  			map[int64]*types.ValidatorSet{
   312  				1: vals,
   313  				2: vals,
   314  				3: newVals,
   315  			},
   316  			false,
   317  			false,
   318  		},
   319  		{
   320  			"bad: last header signed by newVals, interim header has no signers",
   321  			map[int64]*types.SignedHeader{
   322  				// trusted header
   323  				1: h1,
   324  				// last header (0/4 of the original val set signed)
   325  				2: keys.GenSignedHeader(chainID, 2, bTime.Add(1*time.Hour), nil, vals, newVals,
   326  					hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, 0),
   327  				// last header (0/4 of the original val set signed)
   328  				3: newKeys.GenSignedHeader(chainID, 3, bTime.Add(2*time.Hour), nil, newVals, newVals,
   329  					hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(newKeys)),
   330  			},
   331  			map[int64]*types.ValidatorSet{
   332  				1: vals,
   333  				2: vals,
   334  				3: newVals,
   335  			},
   336  			false,
   337  			true,
   338  		},
   339  	}
   340  
   341  	for _, tc := range testCases {
   342  		tc := tc
   343  		t.Run(tc.name, func(t *testing.T) {
   344  			c, err := light.NewClient(
   345  				ctx,
   346  				chainID,
   347  				trustOptions,
   348  				mockp.New(
   349  					chainID,
   350  					tc.otherHeaders,
   351  					tc.vals,
   352  				),
   353  				[]provider.Provider{mockp.New(
   354  					chainID,
   355  					tc.otherHeaders,
   356  					tc.vals,
   357  				)},
   358  				dbs.New(memdb.NewDB(), chainID),
   359  				light.SkippingVerification(light.DefaultTrustLevel),
   360  				light.Logger(log.TestingLogger()),
   361  			)
   362  			if tc.initErr {
   363  				require.Error(t, err)
   364  				return
   365  			}
   366  
   367  			require.NoError(t, err)
   368  
   369  			_, err = c.VerifyLightBlockAtHeight(ctx, 3, bTime.Add(3*time.Hour))
   370  			if tc.verifyErr {
   371  				assert.Error(t, err)
   372  			} else {
   373  				assert.NoError(t, err)
   374  			}
   375  		})
   376  	}
   377  
   378  }
   379  
   380  // start from a large light block to make sure that the pivot height doesn't select a height outside
   381  // the appropriate range
   382  func TestClientLargeBisectionVerification(t *testing.T) {
   383  	veryLargeFullNode := mockp.New(genMockNode(chainID, 100, 3, 0, bTime))
   384  	trustedLightBlock, err := veryLargeFullNode.LightBlock(ctx, 5)
   385  	require.NoError(t, err)
   386  	c, err := light.NewClient(
   387  		ctx,
   388  		chainID,
   389  		light.TrustOptions{
   390  			Period: 4 * time.Hour,
   391  			Height: trustedLightBlock.Height,
   392  			Hash:   trustedLightBlock.Hash(),
   393  		},
   394  		veryLargeFullNode,
   395  		[]provider.Provider{veryLargeFullNode},
   396  		dbs.New(memdb.NewDB(), chainID),
   397  		light.SkippingVerification(light.DefaultTrustLevel),
   398  	)
   399  	require.NoError(t, err)
   400  	h, err := c.Update(ctx, bTime.Add(100*time.Minute))
   401  	assert.NoError(t, err)
   402  	h2, err := veryLargeFullNode.LightBlock(ctx, 100)
   403  	require.NoError(t, err)
   404  	assert.Equal(t, h, h2)
   405  }
   406  
   407  func TestClientBisectionBetweenTrustedHeaders(t *testing.T) {
   408  	c, err := light.NewClient(
   409  		ctx,
   410  		chainID,
   411  		light.TrustOptions{
   412  			Period: 4 * time.Hour,
   413  			Height: 1,
   414  			Hash:   h1.Hash(),
   415  		},
   416  		fullNode,
   417  		[]provider.Provider{fullNode},
   418  		dbs.New(memdb.NewDB(), chainID),
   419  		light.SkippingVerification(light.DefaultTrustLevel),
   420  	)
   421  	require.NoError(t, err)
   422  
   423  	_, err = c.VerifyLightBlockAtHeight(ctx, 3, bTime.Add(2*time.Hour))
   424  	require.NoError(t, err)
   425  
   426  	// confirm that the client already doesn't have the light block
   427  	_, err = c.TrustedLightBlock(2)
   428  	require.Error(t, err)
   429  
   430  	// verify using bisection the light block between the two trusted light blocks
   431  	_, err = c.VerifyLightBlockAtHeight(ctx, 2, bTime.Add(1*time.Hour))
   432  	assert.NoError(t, err)
   433  }
   434  
   435  func TestClient_Cleanup(t *testing.T) {
   436  	c, err := light.NewClient(
   437  		ctx,
   438  		chainID,
   439  		trustOptions,
   440  		fullNode,
   441  		[]provider.Provider{fullNode},
   442  		dbs.New(memdb.NewDB(), chainID),
   443  		light.Logger(log.TestingLogger()),
   444  	)
   445  	require.NoError(t, err)
   446  	_, err = c.TrustedLightBlock(1)
   447  	require.NoError(t, err)
   448  
   449  	err = c.Cleanup()
   450  	require.NoError(t, err)
   451  
   452  	// Check no light blocks exist after Cleanup.
   453  	l, err := c.TrustedLightBlock(1)
   454  	assert.Error(t, err)
   455  	assert.Nil(t, l)
   456  }
   457  
   458  // trustedHeader.Height == options.Height
   459  func TestClientRestoresTrustedHeaderAfterStartup1(t *testing.T) {
   460  	// 1. options.Hash == trustedHeader.Hash
   461  	{
   462  		trustedStore := dbs.New(memdb.NewDB(), chainID)
   463  		err := trustedStore.SaveLightBlock(l1)
   464  		require.NoError(t, err)
   465  
   466  		c, err := light.NewClient(
   467  			ctx,
   468  			chainID,
   469  			trustOptions,
   470  			fullNode,
   471  			[]provider.Provider{fullNode},
   472  			trustedStore,
   473  			light.Logger(log.TestingLogger()),
   474  		)
   475  		require.NoError(t, err)
   476  
   477  		l, err := c.TrustedLightBlock(1)
   478  		assert.NoError(t, err)
   479  		assert.NotNil(t, l)
   480  		assert.Equal(t, l.Hash(), h1.Hash())
   481  		assert.Equal(t, l.ValidatorSet.Hash(), h1.ValidatorsHash.Bytes())
   482  	}
   483  
   484  	// 2. options.Hash != trustedHeader.Hash
   485  	{
   486  		trustedStore := dbs.New(memdb.NewDB(), chainID)
   487  		err := trustedStore.SaveLightBlock(l1)
   488  		require.NoError(t, err)
   489  
   490  		// header1 != h1
   491  		header1 := keys.GenSignedHeader(chainID, 1, bTime.Add(1*time.Hour), nil, vals, vals,
   492  			hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys))
   493  
   494  		primary := mockp.New(
   495  			chainID,
   496  			map[int64]*types.SignedHeader{
   497  				// trusted header
   498  				1: header1,
   499  			},
   500  			valSet,
   501  		)
   502  
   503  		c, err := light.NewClient(
   504  			ctx,
   505  			chainID,
   506  			light.TrustOptions{
   507  				Period: 4 * time.Hour,
   508  				Height: 1,
   509  				Hash:   header1.Hash(),
   510  			},
   511  			primary,
   512  			[]provider.Provider{primary},
   513  			trustedStore,
   514  			light.Logger(log.TestingLogger()),
   515  		)
   516  		require.NoError(t, err)
   517  
   518  		l, err := c.TrustedLightBlock(1)
   519  		assert.NoError(t, err)
   520  		if assert.NotNil(t, l) {
   521  			assert.Equal(t, l.Hash(), header1.Hash())
   522  			assert.NoError(t, l.ValidateBasic(chainID))
   523  		}
   524  	}
   525  }
   526  
   527  // trustedHeader.Height < options.Height
   528  func TestClientRestoresTrustedHeaderAfterStartup2(t *testing.T) {
   529  	// 1. options.Hash == trustedHeader.Hash
   530  	{
   531  		trustedStore := dbs.New(memdb.NewDB(), chainID)
   532  		err := trustedStore.SaveLightBlock(l1)
   533  		require.NoError(t, err)
   534  
   535  		c, err := light.NewClient(
   536  			ctx,
   537  			chainID,
   538  			light.TrustOptions{
   539  				Period: 4 * time.Hour,
   540  				Height: 2,
   541  				Hash:   h2.Hash(),
   542  			},
   543  			fullNode,
   544  			[]provider.Provider{fullNode},
   545  			trustedStore,
   546  			light.Logger(log.TestingLogger()),
   547  		)
   548  		require.NoError(t, err)
   549  
   550  		// Check we still have the 1st header (+header+).
   551  		l, err := c.TrustedLightBlock(1)
   552  		assert.NoError(t, err)
   553  		assert.NotNil(t, l)
   554  		assert.Equal(t, l.Hash(), h1.Hash())
   555  		assert.NoError(t, l.ValidateBasic(chainID))
   556  	}
   557  
   558  	// 2. options.Hash != trustedHeader.Hash
   559  	// This could happen if previous provider was lying to us.
   560  	{
   561  		trustedStore := dbs.New(memdb.NewDB(), chainID)
   562  		err := trustedStore.SaveLightBlock(l1)
   563  		require.NoError(t, err)
   564  
   565  		// header1 != header
   566  		diffHeader1 := keys.GenSignedHeader(chainID, 1, bTime.Add(1*time.Hour), nil, vals, vals,
   567  			hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys))
   568  
   569  		diffHeader2 := keys.GenSignedHeader(chainID, 2, bTime.Add(2*time.Hour), nil, vals, vals,
   570  			hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys))
   571  
   572  		primary := mockp.New(
   573  			chainID,
   574  			map[int64]*types.SignedHeader{
   575  				1: diffHeader1,
   576  				2: diffHeader2,
   577  			},
   578  			valSet,
   579  		)
   580  
   581  		c, err := light.NewClient(
   582  			ctx,
   583  			chainID,
   584  			light.TrustOptions{
   585  				Period: 4 * time.Hour,
   586  				Height: 2,
   587  				Hash:   diffHeader2.Hash(),
   588  			},
   589  			primary,
   590  			[]provider.Provider{primary},
   591  			trustedStore,
   592  			light.Logger(log.TestingLogger()),
   593  		)
   594  		require.NoError(t, err)
   595  
   596  		// Check we no longer have the invalid 1st header (+header+).
   597  		l, err := c.TrustedLightBlock(1)
   598  		assert.Error(t, err)
   599  		assert.Nil(t, l)
   600  	}
   601  }
   602  
   603  // trustedHeader.Height > options.Height
   604  func TestClientRestoresTrustedHeaderAfterStartup3(t *testing.T) {
   605  	// 1. options.Hash == trustedHeader.Hash
   606  	{
   607  		// load the first three headers into the trusted store
   608  		trustedStore := dbs.New(memdb.NewDB(), chainID)
   609  		err := trustedStore.SaveLightBlock(l1)
   610  		require.NoError(t, err)
   611  
   612  		err = trustedStore.SaveLightBlock(l2)
   613  		require.NoError(t, err)
   614  
   615  		c, err := light.NewClient(
   616  			ctx,
   617  			chainID,
   618  			trustOptions,
   619  			fullNode,
   620  			[]provider.Provider{fullNode},
   621  			trustedStore,
   622  			light.Logger(log.TestingLogger()),
   623  		)
   624  		require.NoError(t, err)
   625  
   626  		// Check we still have the 1st light block.
   627  		l, err := c.TrustedLightBlock(1)
   628  		assert.NoError(t, err)
   629  		assert.NotNil(t, l)
   630  		assert.Equal(t, l.Hash(), h1.Hash())
   631  		assert.NoError(t, l.ValidateBasic(chainID))
   632  
   633  		// Check we no longer have 2nd light block.
   634  		l, err = c.TrustedLightBlock(2)
   635  		assert.Error(t, err)
   636  		assert.Nil(t, l)
   637  
   638  		l, err = c.TrustedLightBlock(3)
   639  		assert.Error(t, err)
   640  		assert.Nil(t, l)
   641  	}
   642  
   643  	// 2. options.Hash != trustedHeader.Hash
   644  	// This could happen if previous provider was lying to us.
   645  	{
   646  		trustedStore := dbs.New(memdb.NewDB(), chainID)
   647  		err := trustedStore.SaveLightBlock(l1)
   648  		require.NoError(t, err)
   649  
   650  		// header1 != header
   651  		header1 := keys.GenSignedHeader(chainID, 1, bTime.Add(1*time.Hour), nil, vals, vals,
   652  			hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys))
   653  
   654  		header2 := keys.GenSignedHeader(chainID, 2, bTime.Add(2*time.Hour), nil, vals, vals,
   655  			hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys))
   656  		err = trustedStore.SaveLightBlock(&types.LightBlock{
   657  			SignedHeader: header2,
   658  			ValidatorSet: vals,
   659  		})
   660  		require.NoError(t, err)
   661  
   662  		primary := mockp.New(
   663  			chainID,
   664  			map[int64]*types.SignedHeader{
   665  				1: header1,
   666  			},
   667  			valSet,
   668  		)
   669  
   670  		c, err := light.NewClient(
   671  			ctx,
   672  			chainID,
   673  			light.TrustOptions{
   674  				Period: 4 * time.Hour,
   675  				Height: 1,
   676  				Hash:   header1.Hash(),
   677  			},
   678  			primary,
   679  			[]provider.Provider{primary},
   680  			trustedStore,
   681  			light.Logger(log.TestingLogger()),
   682  		)
   683  		require.NoError(t, err)
   684  
   685  		// Check we have swapped invalid 1st light block (+lightblock+) with correct one (+lightblock2+).
   686  		l, err := c.TrustedLightBlock(1)
   687  		assert.NoError(t, err)
   688  		assert.NotNil(t, l)
   689  		assert.Equal(t, l.Hash(), header1.Hash())
   690  		assert.NoError(t, l.ValidateBasic(chainID))
   691  
   692  		// Check we no longer have invalid 2nd light block (+lightblock2+).
   693  		l, err = c.TrustedLightBlock(2)
   694  		assert.Error(t, err)
   695  		assert.Nil(t, l)
   696  	}
   697  }
   698  
   699  func TestClient_Update(t *testing.T) {
   700  	c, err := light.NewClient(
   701  		ctx,
   702  		chainID,
   703  		trustOptions,
   704  		fullNode,
   705  		[]provider.Provider{fullNode},
   706  		dbs.New(memdb.NewDB(), chainID),
   707  		light.Logger(log.TestingLogger()),
   708  	)
   709  	require.NoError(t, err)
   710  
   711  	// should result in downloading & verifying header #3
   712  	l, err := c.Update(ctx, bTime.Add(2*time.Hour))
   713  	assert.NoError(t, err)
   714  	if assert.NotNil(t, l) {
   715  		assert.EqualValues(t, 3, l.Height)
   716  		assert.NoError(t, l.ValidateBasic(chainID))
   717  	}
   718  }
   719  
   720  func TestClient_Concurrency(t *testing.T) {
   721  	c, err := light.NewClient(
   722  		ctx,
   723  		chainID,
   724  		trustOptions,
   725  		fullNode,
   726  		[]provider.Provider{fullNode},
   727  		dbs.New(memdb.NewDB(), chainID),
   728  		light.Logger(log.TestingLogger()),
   729  	)
   730  	require.NoError(t, err)
   731  
   732  	_, err = c.VerifyLightBlockAtHeight(ctx, 2, bTime.Add(2*time.Hour))
   733  	require.NoError(t, err)
   734  
   735  	var wg sync.WaitGroup
   736  	for i := 0; i < 100; i++ {
   737  		wg.Add(1)
   738  		go func() {
   739  			defer wg.Done()
   740  
   741  			// NOTE: Cleanup, Stop, VerifyLightBlockAtHeight and Verify are not supposed
   742  			// to be concurrenly safe.
   743  
   744  			assert.Equal(t, chainID, c.ChainID())
   745  
   746  			_, err := c.LastTrustedHeight()
   747  			assert.NoError(t, err)
   748  
   749  			_, err = c.FirstTrustedHeight()
   750  			assert.NoError(t, err)
   751  
   752  			l, err := c.TrustedLightBlock(1)
   753  			assert.NoError(t, err)
   754  			assert.NotNil(t, l)
   755  		}()
   756  	}
   757  
   758  	wg.Wait()
   759  }
   760  
   761  func TestClientReplacesPrimaryWithWitnessIfPrimaryIsUnavailable(t *testing.T) {
   762  	c, err := light.NewClient(
   763  		ctx,
   764  		chainID,
   765  		trustOptions,
   766  		deadNode,
   767  		[]provider.Provider{fullNode, fullNode},
   768  		dbs.New(memdb.NewDB(), chainID),
   769  		light.Logger(log.TestingLogger()),
   770  		light.MaxRetryAttempts(1),
   771  	)
   772  
   773  	require.NoError(t, err)
   774  	_, err = c.Update(ctx, bTime.Add(2*time.Hour))
   775  	require.NoError(t, err)
   776  
   777  	assert.NotEqual(t, c.Primary(), deadNode)
   778  	assert.Equal(t, 1, len(c.Witnesses()))
   779  }
   780  
   781  func TestClient_BackwardsVerification(t *testing.T) {
   782  	{
   783  		trustHeader, _ := largeFullNode.LightBlock(ctx, 6)
   784  		c, err := light.NewClient(
   785  			ctx,
   786  			chainID,
   787  			light.TrustOptions{
   788  				Period: 4 * time.Minute,
   789  				Height: trustHeader.Height,
   790  				Hash:   trustHeader.Hash(),
   791  			},
   792  			largeFullNode,
   793  			[]provider.Provider{largeFullNode},
   794  			dbs.New(memdb.NewDB(), chainID),
   795  			light.Logger(log.TestingLogger()),
   796  		)
   797  		require.NoError(t, err)
   798  
   799  		// 1) verify before the trusted header using backwards => expect no error
   800  		h, err := c.VerifyLightBlockAtHeight(ctx, 5, bTime.Add(6*time.Minute))
   801  		require.NoError(t, err)
   802  		if assert.NotNil(t, h) {
   803  			assert.EqualValues(t, 5, h.Height)
   804  		}
   805  
   806  		// 2) untrusted header is expired but trusted header is not => expect no error
   807  		h, err = c.VerifyLightBlockAtHeight(ctx, 3, bTime.Add(8*time.Minute))
   808  		assert.NoError(t, err)
   809  		assert.NotNil(t, h)
   810  
   811  		// 3) already stored headers should return the header without error
   812  		h, err = c.VerifyLightBlockAtHeight(ctx, 5, bTime.Add(6*time.Minute))
   813  		assert.NoError(t, err)
   814  		assert.NotNil(t, h)
   815  
   816  		// 4a) First verify latest header
   817  		_, err = c.VerifyLightBlockAtHeight(ctx, 9, bTime.Add(9*time.Minute))
   818  		require.NoError(t, err)
   819  
   820  		// 4b) Verify backwards using bisection => expect no error
   821  		_, err = c.VerifyLightBlockAtHeight(ctx, 7, bTime.Add(9*time.Minute))
   822  		assert.NoError(t, err)
   823  		// shouldn't have verified this header in the process
   824  		_, err = c.TrustedLightBlock(8)
   825  		assert.Error(t, err)
   826  
   827  		// 5) Try bisection method, but closest header (at 7) has expired
   828  		// so expect error
   829  		_, err = c.VerifyLightBlockAtHeight(ctx, 8, bTime.Add(12*time.Minute))
   830  		assert.Error(t, err)
   831  
   832  	}
   833  	{
   834  		testCases := []struct {
   835  			provider provider.Provider
   836  		}{
   837  			{
   838  				// 7) provides incorrect height
   839  				mockp.New(
   840  					chainID,
   841  					map[int64]*types.SignedHeader{
   842  						1: h1,
   843  						2: keys.GenSignedHeader(chainID, 1, bTime.Add(30*time.Minute), nil, vals, vals,
   844  							hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys)),
   845  						3: h3,
   846  					},
   847  					valSet,
   848  				),
   849  			},
   850  			{
   851  				// 8) provides incorrect hash
   852  				mockp.New(
   853  					chainID,
   854  					map[int64]*types.SignedHeader{
   855  						1: h1,
   856  						2: keys.GenSignedHeader(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals,
   857  							hash("app_hash2"), hash("cons_hash23"), hash("results_hash30"), 0, len(keys)),
   858  						3: h3,
   859  					},
   860  					valSet,
   861  				),
   862  			},
   863  		}
   864  
   865  		for idx, tc := range testCases {
   866  			c, err := light.NewClient(
   867  				ctx,
   868  				chainID,
   869  				light.TrustOptions{
   870  					Period: 1 * time.Hour,
   871  					Height: 3,
   872  					Hash:   h3.Hash(),
   873  				},
   874  				tc.provider,
   875  				[]provider.Provider{tc.provider},
   876  				dbs.New(memdb.NewDB(), chainID),
   877  				light.Logger(log.TestingLogger()),
   878  			)
   879  			require.NoError(t, err, idx)
   880  
   881  			_, err = c.VerifyLightBlockAtHeight(ctx, 2, bTime.Add(1*time.Hour).Add(1*time.Second))
   882  			assert.Error(t, err, idx)
   883  		}
   884  	}
   885  }
   886  
   887  func TestClient_NewClientFromTrustedStore(t *testing.T) {
   888  	// 1) Initiate DB and fill with a "trusted" header
   889  	db := dbs.New(memdb.NewDB(), chainID)
   890  	err := db.SaveLightBlock(l1)
   891  	require.NoError(t, err)
   892  
   893  	c, err := light.NewClientFromTrustedStore(
   894  		chainID,
   895  		trustPeriod,
   896  		deadNode,
   897  		[]provider.Provider{deadNode},
   898  		db,
   899  	)
   900  	require.NoError(t, err)
   901  
   902  	// 2) Check light block exists (deadNode is being used to ensure we're not getting
   903  	// it from primary)
   904  	h, err := c.TrustedLightBlock(1)
   905  	assert.NoError(t, err)
   906  	assert.EqualValues(t, l1.Height, h.Height)
   907  }
   908  
   909  func TestClientRemovesWitnessIfItSendsUsIncorrectHeader(t *testing.T) {
   910  	// different headers hash then primary plus less than 1/3 signed (no fork)
   911  	badProvider1 := mockp.New(
   912  		chainID,
   913  		map[int64]*types.SignedHeader{
   914  			1: h1,
   915  			2: keys.GenSignedHeaderLastBlockID(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals,
   916  				hash("app_hash2"), hash("cons_hash"), hash("results_hash"),
   917  				len(keys), len(keys), types.BlockID{Hash: h1.Hash()}),
   918  		},
   919  		map[int64]*types.ValidatorSet{
   920  			1: vals,
   921  			2: vals,
   922  		},
   923  	)
   924  	// header is empty
   925  	badProvider2 := mockp.New(
   926  		chainID,
   927  		map[int64]*types.SignedHeader{
   928  			1: h1,
   929  			2: h2,
   930  		},
   931  		map[int64]*types.ValidatorSet{
   932  			1: vals,
   933  			2: vals,
   934  		},
   935  	)
   936  
   937  	lb1, _ := badProvider1.LightBlock(ctx, 2)
   938  	require.NotEqual(t, lb1.Hash(), l1.Hash())
   939  
   940  	c, err := light.NewClient(
   941  		ctx,
   942  		chainID,
   943  		trustOptions,
   944  		fullNode,
   945  		[]provider.Provider{badProvider1, badProvider2},
   946  		dbs.New(memdb.NewDB(), chainID),
   947  		light.Logger(log.TestingLogger()),
   948  		light.MaxRetryAttempts(1),
   949  	)
   950  	// witness should have behaved properly -> no error
   951  	require.NoError(t, err)
   952  	assert.EqualValues(t, 2, len(c.Witnesses()))
   953  
   954  	// witness behaves incorrectly -> removed from list, no error
   955  	l, err := c.VerifyLightBlockAtHeight(ctx, 2, bTime.Add(2*time.Hour))
   956  	assert.NoError(t, err)
   957  	assert.EqualValues(t, 1, len(c.Witnesses()))
   958  	// light block should still be verified
   959  	assert.EqualValues(t, 2, l.Height)
   960  
   961  	// remaining witnesses don't have light block -> error
   962  	_, err = c.VerifyLightBlockAtHeight(ctx, 3, bTime.Add(2*time.Hour))
   963  	if assert.Error(t, err) {
   964  		assert.Equal(t, light.ErrFailedHeaderCrossReferencing, err)
   965  	}
   966  	// witness does not have a light block -> left in the list
   967  	assert.EqualValues(t, 1, len(c.Witnesses()))
   968  }
   969  
   970  func TestClient_TrustedValidatorSet(t *testing.T) {
   971  	differentVals, _ := types.RandValidatorSet(10, 100)
   972  	badValSetNode := mockp.New(
   973  		chainID,
   974  		map[int64]*types.SignedHeader{
   975  			1: h1,
   976  			// 3/3 signed, but validator set at height 2 below is invalid -> witness
   977  			// should be removed.
   978  			2: keys.GenSignedHeaderLastBlockID(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals,
   979  				hash("app_hash2"), hash("cons_hash"), hash("results_hash"),
   980  				0, len(keys), types.BlockID{Hash: h1.Hash()}),
   981  			3: h3,
   982  		},
   983  		map[int64]*types.ValidatorSet{
   984  			1: vals,
   985  			2: differentVals,
   986  			3: differentVals,
   987  		},
   988  	)
   989  
   990  	c, err := light.NewClient(
   991  		ctx,
   992  		chainID,
   993  		trustOptions,
   994  		fullNode,
   995  		[]provider.Provider{badValSetNode, fullNode},
   996  		dbs.New(memdb.NewDB(), chainID),
   997  		light.Logger(log.TestingLogger()),
   998  	)
   999  	require.NoError(t, err)
  1000  	assert.Equal(t, 2, len(c.Witnesses()))
  1001  
  1002  	_, err = c.VerifyLightBlockAtHeight(ctx, 2, bTime.Add(2*time.Hour).Add(1*time.Second))
  1003  	assert.NoError(t, err)
  1004  	assert.Equal(t, 1, len(c.Witnesses()))
  1005  }
  1006  
  1007  func TestClientPrunesHeadersAndValidatorSets(t *testing.T) {
  1008  	c, err := light.NewClient(
  1009  		ctx,
  1010  		chainID,
  1011  		trustOptions,
  1012  		fullNode,
  1013  		[]provider.Provider{fullNode},
  1014  		dbs.New(memdb.NewDB(), chainID),
  1015  		light.Logger(log.TestingLogger()),
  1016  		light.PruningSize(1),
  1017  	)
  1018  	require.NoError(t, err)
  1019  	_, err = c.TrustedLightBlock(1)
  1020  	require.NoError(t, err)
  1021  
  1022  	h, err := c.Update(ctx, bTime.Add(2*time.Hour))
  1023  	require.NoError(t, err)
  1024  	require.Equal(t, int64(3), h.Height)
  1025  
  1026  	_, err = c.TrustedLightBlock(1)
  1027  	assert.Error(t, err)
  1028  }
  1029  
  1030  func TestClientEnsureValidHeadersAndValSets(t *testing.T) {
  1031  	emptyValSet := &types.ValidatorSet{
  1032  		Validators: nil,
  1033  		Proposer:   nil,
  1034  	}
  1035  
  1036  	testCases := []struct {
  1037  		headers map[int64]*types.SignedHeader
  1038  		vals    map[int64]*types.ValidatorSet
  1039  		err     bool
  1040  	}{
  1041  		{
  1042  			headerSet,
  1043  			valSet,
  1044  			false,
  1045  		},
  1046  		{
  1047  			headerSet,
  1048  			map[int64]*types.ValidatorSet{
  1049  				1: vals,
  1050  				2: vals,
  1051  				3: nil,
  1052  			},
  1053  			true,
  1054  		},
  1055  		{
  1056  			map[int64]*types.SignedHeader{
  1057  				1: h1,
  1058  				2: h2,
  1059  				3: nil,
  1060  			},
  1061  			valSet,
  1062  			true,
  1063  		},
  1064  		{
  1065  			headerSet,
  1066  			map[int64]*types.ValidatorSet{
  1067  				1: vals,
  1068  				2: vals,
  1069  				3: emptyValSet,
  1070  			},
  1071  			true,
  1072  		},
  1073  	}
  1074  
  1075  	for _, tc := range testCases {
  1076  		badNode := mockp.New(
  1077  			chainID,
  1078  			tc.headers,
  1079  			tc.vals,
  1080  		)
  1081  		c, err := light.NewClient(
  1082  			ctx,
  1083  			chainID,
  1084  			trustOptions,
  1085  			badNode,
  1086  			[]provider.Provider{badNode, badNode},
  1087  			dbs.New(memdb.NewDB(), chainID),
  1088  			light.MaxRetryAttempts(1),
  1089  		)
  1090  		require.NoError(t, err)
  1091  
  1092  		_, err = c.VerifyLightBlockAtHeight(ctx, 3, bTime.Add(2*time.Hour))
  1093  		if tc.err {
  1094  			assert.Error(t, err)
  1095  		} else {
  1096  			assert.NoError(t, err)
  1097  		}
  1098  	}
  1099  
  1100  }