github.com/number571/tendermint@v0.34.11-gost/light/client_test.go (about)

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