github.com/vipernet-xyz/tm@v0.34.24/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/vipernet-xyz/tm/libs/log"
    16  	"github.com/vipernet-xyz/tm/light"
    17  	"github.com/vipernet-xyz/tm/light/provider"
    18  	mockp "github.com/vipernet-xyz/tm/light/provider/mock"
    19  	dbs "github.com/vipernet-xyz/tm/light/store/db"
    20  	"github.com/vipernet-xyz/tm/types"
    21  )
    22  
    23  const (
    24  	chainID = "test"
    25  )
    26  
    27  var (
    28  	ctx      = context.Background()
    29  	keys     = genPrivKeys(4)
    30  	vals     = keys.ToValidators(20, 10)
    31  	bTime, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
    32  	h1       = keys.GenSignedHeader(chainID, 1, bTime, nil, vals, vals,
    33  		hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys))
    34  	// 3/3 signed
    35  	h2 = keys.GenSignedHeaderLastBlockID(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals,
    36  		hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys), types.BlockID{Hash: h1.Hash()})
    37  	// 3/3 signed
    38  	h3 = keys.GenSignedHeaderLastBlockID(chainID, 3, bTime.Add(1*time.Hour), nil, vals, vals,
    39  		hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys), types.BlockID{Hash: h2.Hash()})
    40  	trustPeriod  = 4 * time.Hour
    41  	trustOptions = light.TrustOptions{
    42  		Period: 4 * time.Hour,
    43  		Height: 1,
    44  		Hash:   h1.Hash(),
    45  	}
    46  	valSet = map[int64]*types.ValidatorSet{
    47  		1: vals,
    48  		2: vals,
    49  		3: vals,
    50  		4: vals,
    51  	}
    52  	headerSet = map[int64]*types.SignedHeader{
    53  		1: h1,
    54  		// interim header (3/3 signed)
    55  		2: h2,
    56  		// last header (3/3 signed)
    57  		3: h3,
    58  	}
    59  	l1       = &types.LightBlock{SignedHeader: h1, ValidatorSet: vals}
    60  	l2       = &types.LightBlock{SignedHeader: h2, 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, _ := types.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(), chainID),
   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(), chainID),
   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(), chainID),
   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(), chainID),
   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(), chainID),
   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 TestClientRestoresTrustedHeaderAfterStartup1(t *testing.T) {
   462  	// 1. options.Hash == trustedHeader.Hash
   463  	{
   464  		trustedStore := dbs.New(dbm.NewMemDB(), chainID)
   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(), chainID)
   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  			assert.Equal(t, l.Hash(), header1.Hash())
   524  			assert.NoError(t, l.ValidateBasic(chainID))
   525  		}
   526  	}
   527  }
   528  
   529  // trustedHeader.Height < options.Height
   530  func TestClientRestoresTrustedHeaderAfterStartup2(t *testing.T) {
   531  	// 1. options.Hash == trustedHeader.Hash
   532  	{
   533  		trustedStore := dbs.New(dbm.NewMemDB(), chainID)
   534  		err := trustedStore.SaveLightBlock(l1)
   535  		require.NoError(t, err)
   536  
   537  		c, err := light.NewClient(
   538  			ctx,
   539  			chainID,
   540  			light.TrustOptions{
   541  				Period: 4 * time.Hour,
   542  				Height: 2,
   543  				Hash:   h2.Hash(),
   544  			},
   545  			fullNode,
   546  			[]provider.Provider{fullNode},
   547  			trustedStore,
   548  			light.Logger(log.TestingLogger()),
   549  		)
   550  		require.NoError(t, err)
   551  
   552  		// Check we still have the 1st header (+header+).
   553  		l, err := c.TrustedLightBlock(1)
   554  		assert.NoError(t, err)
   555  		assert.NotNil(t, l)
   556  		assert.Equal(t, l.Hash(), h1.Hash())
   557  		assert.NoError(t, l.ValidateBasic(chainID))
   558  	}
   559  
   560  	// 2. options.Hash != trustedHeader.Hash
   561  	// This could happen if previous provider was lying to us.
   562  	{
   563  		trustedStore := dbs.New(dbm.NewMemDB(), chainID)
   564  		err := trustedStore.SaveLightBlock(l1)
   565  		require.NoError(t, err)
   566  
   567  		// header1 != header
   568  		diffHeader1 := keys.GenSignedHeader(chainID, 1, bTime.Add(1*time.Hour), nil, vals, vals,
   569  			hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys))
   570  
   571  		diffHeader2 := keys.GenSignedHeader(chainID, 2, bTime.Add(2*time.Hour), nil, vals, vals,
   572  			hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys))
   573  
   574  		primary := mockp.New(
   575  			chainID,
   576  			map[int64]*types.SignedHeader{
   577  				1: diffHeader1,
   578  				2: diffHeader2,
   579  			},
   580  			valSet,
   581  		)
   582  
   583  		c, err := light.NewClient(
   584  			ctx,
   585  			chainID,
   586  			light.TrustOptions{
   587  				Period: 4 * time.Hour,
   588  				Height: 2,
   589  				Hash:   diffHeader2.Hash(),
   590  			},
   591  			primary,
   592  			[]provider.Provider{primary},
   593  			trustedStore,
   594  			light.Logger(log.TestingLogger()),
   595  		)
   596  		require.NoError(t, err)
   597  
   598  		// Check we no longer have the invalid 1st header (+header+).
   599  		l, err := c.TrustedLightBlock(1)
   600  		assert.Error(t, err)
   601  		assert.Nil(t, l)
   602  	}
   603  }
   604  
   605  // trustedHeader.Height > options.Height
   606  func TestClientRestoresTrustedHeaderAfterStartup3(t *testing.T) {
   607  	// 1. options.Hash == trustedHeader.Hash
   608  	{
   609  		// load the first three headers into the trusted store
   610  		trustedStore := dbs.New(dbm.NewMemDB(), chainID)
   611  		err := trustedStore.SaveLightBlock(l1)
   612  		require.NoError(t, err)
   613  
   614  		err = trustedStore.SaveLightBlock(l2)
   615  		require.NoError(t, err)
   616  
   617  		c, err := light.NewClient(
   618  			ctx,
   619  			chainID,
   620  			trustOptions,
   621  			fullNode,
   622  			[]provider.Provider{fullNode},
   623  			trustedStore,
   624  			light.Logger(log.TestingLogger()),
   625  		)
   626  		require.NoError(t, err)
   627  
   628  		// Check we still have the 1st light block.
   629  		l, err := c.TrustedLightBlock(1)
   630  		assert.NoError(t, err)
   631  		assert.NotNil(t, l)
   632  		assert.Equal(t, l.Hash(), h1.Hash())
   633  		assert.NoError(t, l.ValidateBasic(chainID))
   634  
   635  		// Check we no longer have 2nd light block.
   636  		l, err = c.TrustedLightBlock(2)
   637  		assert.Error(t, err)
   638  		assert.Nil(t, l)
   639  
   640  		l, err = c.TrustedLightBlock(3)
   641  		assert.Error(t, err)
   642  		assert.Nil(t, l)
   643  	}
   644  
   645  	// 2. options.Hash != trustedHeader.Hash
   646  	// This could happen if previous provider was lying to us.
   647  	{
   648  		trustedStore := dbs.New(dbm.NewMemDB(), chainID)
   649  		err := trustedStore.SaveLightBlock(l1)
   650  		require.NoError(t, err)
   651  
   652  		// header1 != header
   653  		header1 := keys.GenSignedHeader(chainID, 1, bTime.Add(1*time.Hour), nil, vals, vals,
   654  			hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys))
   655  
   656  		header2 := keys.GenSignedHeader(chainID, 2, bTime.Add(2*time.Hour), nil, vals, vals,
   657  			hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys))
   658  		err = trustedStore.SaveLightBlock(&types.LightBlock{
   659  			SignedHeader: header2,
   660  			ValidatorSet: vals,
   661  		})
   662  		require.NoError(t, err)
   663  
   664  		primary := mockp.New(
   665  			chainID,
   666  			map[int64]*types.SignedHeader{
   667  				1: header1,
   668  			},
   669  			valSet,
   670  		)
   671  
   672  		c, err := light.NewClient(
   673  			ctx,
   674  			chainID,
   675  			light.TrustOptions{
   676  				Period: 4 * time.Hour,
   677  				Height: 1,
   678  				Hash:   header1.Hash(),
   679  			},
   680  			primary,
   681  			[]provider.Provider{primary},
   682  			trustedStore,
   683  			light.Logger(log.TestingLogger()),
   684  		)
   685  		require.NoError(t, err)
   686  
   687  		// Check we have swapped invalid 1st light block (+lightblock+) with correct one (+lightblock2+).
   688  		l, err := c.TrustedLightBlock(1)
   689  		assert.NoError(t, err)
   690  		assert.NotNil(t, l)
   691  		assert.Equal(t, l.Hash(), header1.Hash())
   692  		assert.NoError(t, l.ValidateBasic(chainID))
   693  
   694  		// Check we no longer have invalid 2nd light block (+lightblock2+).
   695  		l, err = c.TrustedLightBlock(2)
   696  		assert.Error(t, err)
   697  		assert.Nil(t, l)
   698  	}
   699  }
   700  
   701  func TestClient_Update(t *testing.T) {
   702  	c, err := light.NewClient(
   703  		ctx,
   704  		chainID,
   705  		trustOptions,
   706  		fullNode,
   707  		[]provider.Provider{fullNode},
   708  		dbs.New(dbm.NewMemDB(), chainID),
   709  		light.Logger(log.TestingLogger()),
   710  	)
   711  	require.NoError(t, err)
   712  
   713  	// should result in downloading & verifying header #3
   714  	l, err := c.Update(ctx, bTime.Add(2*time.Hour))
   715  	assert.NoError(t, err)
   716  	if assert.NotNil(t, l) {
   717  		assert.EqualValues(t, 3, l.Height)
   718  		assert.NoError(t, l.ValidateBasic(chainID))
   719  	}
   720  }
   721  
   722  func TestClient_Concurrency(t *testing.T) {
   723  	c, err := light.NewClient(
   724  		ctx,
   725  		chainID,
   726  		trustOptions,
   727  		fullNode,
   728  		[]provider.Provider{fullNode},
   729  		dbs.New(dbm.NewMemDB(), chainID),
   730  		light.Logger(log.TestingLogger()),
   731  	)
   732  	require.NoError(t, err)
   733  
   734  	_, err = c.VerifyLightBlockAtHeight(ctx, 2, bTime.Add(2*time.Hour))
   735  	require.NoError(t, err)
   736  
   737  	var wg sync.WaitGroup
   738  	for i := 0; i < 100; i++ {
   739  		wg.Add(1)
   740  		go func() {
   741  			defer wg.Done()
   742  
   743  			// NOTE: Cleanup, Stop, VerifyLightBlockAtHeight and Verify are not supposed
   744  			// to be concurrenly safe.
   745  
   746  			assert.Equal(t, chainID, c.ChainID())
   747  
   748  			_, err := c.LastTrustedHeight()
   749  			assert.NoError(t, err)
   750  
   751  			_, err = c.FirstTrustedHeight()
   752  			assert.NoError(t, err)
   753  
   754  			l, err := c.TrustedLightBlock(1)
   755  			assert.NoError(t, err)
   756  			assert.NotNil(t, l)
   757  		}()
   758  	}
   759  
   760  	wg.Wait()
   761  }
   762  
   763  func TestClientReplacesPrimaryWithWitnessIfPrimaryIsUnavailable(t *testing.T) {
   764  	c, err := light.NewClient(
   765  		ctx,
   766  		chainID,
   767  		trustOptions,
   768  		deadNode,
   769  		[]provider.Provider{fullNode, fullNode},
   770  		dbs.New(dbm.NewMemDB(), chainID),
   771  		light.Logger(log.TestingLogger()),
   772  		light.MaxRetryAttempts(1),
   773  	)
   774  
   775  	require.NoError(t, err)
   776  	_, err = c.Update(ctx, bTime.Add(2*time.Hour))
   777  	require.NoError(t, err)
   778  
   779  	assert.NotEqual(t, c.Primary(), deadNode)
   780  	assert.Equal(t, 2, len(c.Witnesses()))
   781  }
   782  
   783  func TestClient_BackwardsVerification(t *testing.T) {
   784  	{
   785  		trustHeader, _ := largeFullNode.LightBlock(ctx, 6)
   786  		c, err := light.NewClient(
   787  			ctx,
   788  			chainID,
   789  			light.TrustOptions{
   790  				Period: 4 * time.Minute,
   791  				Height: trustHeader.Height,
   792  				Hash:   trustHeader.Hash(),
   793  			},
   794  			largeFullNode,
   795  			[]provider.Provider{largeFullNode},
   796  			dbs.New(dbm.NewMemDB(), chainID),
   797  			light.Logger(log.TestingLogger()),
   798  		)
   799  		require.NoError(t, err)
   800  
   801  		// 1) verify before the trusted header using backwards => expect no error
   802  		h, err := c.VerifyLightBlockAtHeight(ctx, 5, bTime.Add(6*time.Minute))
   803  		require.NoError(t, err)
   804  		if assert.NotNil(t, h) {
   805  			assert.EqualValues(t, 5, h.Height)
   806  		}
   807  
   808  		// 2) untrusted header is expired but trusted header is not => expect no error
   809  		h, err = c.VerifyLightBlockAtHeight(ctx, 3, bTime.Add(8*time.Minute))
   810  		assert.NoError(t, err)
   811  		assert.NotNil(t, h)
   812  
   813  		// 3) already stored headers should return the header without error
   814  		h, err = c.VerifyLightBlockAtHeight(ctx, 5, bTime.Add(6*time.Minute))
   815  		assert.NoError(t, err)
   816  		assert.NotNil(t, h)
   817  
   818  		// 4a) First verify latest header
   819  		_, err = c.VerifyLightBlockAtHeight(ctx, 9, bTime.Add(9*time.Minute))
   820  		require.NoError(t, err)
   821  
   822  		// 4b) Verify backwards using bisection => expect no error
   823  		_, err = c.VerifyLightBlockAtHeight(ctx, 7, bTime.Add(9*time.Minute))
   824  		assert.NoError(t, err)
   825  		// shouldn't have verified this header in the process
   826  		_, err = c.TrustedLightBlock(8)
   827  		assert.Error(t, err)
   828  
   829  		// 5) Try bisection method, but closest header (at 7) has expired
   830  		// so expect error
   831  		_, err = c.VerifyLightBlockAtHeight(ctx, 8, bTime.Add(12*time.Minute))
   832  		assert.Error(t, err)
   833  
   834  	}
   835  	{
   836  		testCases := []struct {
   837  			provider provider.Provider
   838  		}{
   839  			{
   840  				// 7) provides incorrect height
   841  				mockp.New(
   842  					chainID,
   843  					map[int64]*types.SignedHeader{
   844  						1: h1,
   845  						2: keys.GenSignedHeader(chainID, 1, bTime.Add(30*time.Minute), nil, vals, vals,
   846  							hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys)),
   847  						3: h3,
   848  					},
   849  					valSet,
   850  				),
   851  			},
   852  			{
   853  				// 8) provides incorrect hash
   854  				mockp.New(
   855  					chainID,
   856  					map[int64]*types.SignedHeader{
   857  						1: h1,
   858  						2: keys.GenSignedHeader(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals,
   859  							hash("app_hash2"), hash("cons_hash23"), hash("results_hash30"), 0, len(keys)),
   860  						3: h3,
   861  					},
   862  					valSet,
   863  				),
   864  			},
   865  		}
   866  
   867  		for idx, tc := range testCases {
   868  			c, err := light.NewClient(
   869  				ctx,
   870  				chainID,
   871  				light.TrustOptions{
   872  					Period: 1 * time.Hour,
   873  					Height: 3,
   874  					Hash:   h3.Hash(),
   875  				},
   876  				tc.provider,
   877  				[]provider.Provider{tc.provider},
   878  				dbs.New(dbm.NewMemDB(), chainID),
   879  				light.Logger(log.TestingLogger()),
   880  			)
   881  			require.NoError(t, err, idx)
   882  
   883  			_, err = c.VerifyLightBlockAtHeight(ctx, 2, bTime.Add(1*time.Hour).Add(1*time.Second))
   884  			assert.Error(t, err, idx)
   885  		}
   886  	}
   887  }
   888  
   889  func TestClient_NewClientFromTrustedStore(t *testing.T) {
   890  	// 1) Initiate DB and fill with a "trusted" header
   891  	db := dbs.New(dbm.NewMemDB(), chainID)
   892  	err := db.SaveLightBlock(l1)
   893  	require.NoError(t, err)
   894  
   895  	c, err := light.NewClientFromTrustedStore(
   896  		chainID,
   897  		trustPeriod,
   898  		deadNode,
   899  		[]provider.Provider{deadNode},
   900  		db,
   901  	)
   902  	require.NoError(t, err)
   903  
   904  	// 2) Check light block exists (deadNode is being used to ensure we're not getting
   905  	// it from primary)
   906  	h, err := c.TrustedLightBlock(1)
   907  	assert.NoError(t, err)
   908  	assert.EqualValues(t, l1.Height, h.Height)
   909  }
   910  
   911  func TestClientRemovesWitnessIfItSendsUsIncorrectHeader(t *testing.T) {
   912  	// different headers hash then primary plus less than 1/3 signed (no fork)
   913  	badProvider1 := mockp.New(
   914  		chainID,
   915  		map[int64]*types.SignedHeader{
   916  			1: h1,
   917  			2: keys.GenSignedHeaderLastBlockID(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals,
   918  				hash("app_hash2"), hash("cons_hash"), hash("results_hash"),
   919  				len(keys), len(keys), types.BlockID{Hash: h1.Hash()}),
   920  		},
   921  		map[int64]*types.ValidatorSet{
   922  			1: vals,
   923  			2: vals,
   924  		},
   925  	)
   926  	// header is empty
   927  	badProvider2 := mockp.New(
   928  		chainID,
   929  		map[int64]*types.SignedHeader{
   930  			1: h1,
   931  			2: h2,
   932  		},
   933  		map[int64]*types.ValidatorSet{
   934  			1: vals,
   935  			2: vals,
   936  		},
   937  	)
   938  
   939  	lb1, _ := badProvider1.LightBlock(ctx, 2)
   940  	require.NotEqual(t, lb1.Hash(), l1.Hash())
   941  
   942  	c, err := light.NewClient(
   943  		ctx,
   944  		chainID,
   945  		trustOptions,
   946  		fullNode,
   947  		[]provider.Provider{badProvider1, badProvider2},
   948  		dbs.New(dbm.NewMemDB(), chainID),
   949  		light.Logger(log.TestingLogger()),
   950  		light.MaxRetryAttempts(1),
   951  	)
   952  	// witness should have behaved properly -> no error
   953  	require.NoError(t, err)
   954  	assert.EqualValues(t, 2, len(c.Witnesses()))
   955  
   956  	// witness behaves incorrectly -> removed from list, no error
   957  	l, err := c.VerifyLightBlockAtHeight(ctx, 2, bTime.Add(2*time.Hour))
   958  	assert.NoError(t, err)
   959  	assert.EqualValues(t, 1, len(c.Witnesses()))
   960  	// light block should still be verified
   961  	assert.EqualValues(t, 2, l.Height)
   962  
   963  	// remaining witnesses don't have light block -> error
   964  	_, err = c.VerifyLightBlockAtHeight(ctx, 3, bTime.Add(2*time.Hour))
   965  	if assert.Error(t, err) {
   966  		assert.Equal(t, light.ErrFailedHeaderCrossReferencing, err)
   967  	}
   968  	// witness does not have a light block -> left in the list
   969  	assert.EqualValues(t, 1, len(c.Witnesses()))
   970  }
   971  
   972  func TestClient_TrustedValidatorSet(t *testing.T) {
   973  	differentVals, _ := types.RandValidatorSet(10, 100)
   974  	badValSetNode := mockp.New(
   975  		chainID,
   976  		map[int64]*types.SignedHeader{
   977  			1: h1,
   978  			// 3/3 signed, but validator set at height 2 below is invalid -> witness
   979  			// should be removed.
   980  			2: keys.GenSignedHeaderLastBlockID(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals,
   981  				hash("app_hash2"), hash("cons_hash"), hash("results_hash"),
   982  				0, len(keys), types.BlockID{Hash: h1.Hash()}),
   983  			3: h3,
   984  		},
   985  		map[int64]*types.ValidatorSet{
   986  			1: vals,
   987  			2: differentVals,
   988  			3: differentVals,
   989  		},
   990  	)
   991  
   992  	c, err := light.NewClient(
   993  		ctx,
   994  		chainID,
   995  		trustOptions,
   996  		fullNode,
   997  		[]provider.Provider{badValSetNode, fullNode},
   998  		dbs.New(dbm.NewMemDB(), chainID),
   999  		light.Logger(log.TestingLogger()),
  1000  	)
  1001  	require.NoError(t, err)
  1002  	assert.Equal(t, 2, len(c.Witnesses()))
  1003  
  1004  	_, err = c.VerifyLightBlockAtHeight(ctx, 2, bTime.Add(2*time.Hour).Add(1*time.Second))
  1005  	assert.NoError(t, err)
  1006  	assert.Equal(t, 1, len(c.Witnesses()))
  1007  }
  1008  
  1009  func TestClientPrunesHeadersAndValidatorSets(t *testing.T) {
  1010  	c, err := light.NewClient(
  1011  		ctx,
  1012  		chainID,
  1013  		trustOptions,
  1014  		fullNode,
  1015  		[]provider.Provider{fullNode},
  1016  		dbs.New(dbm.NewMemDB(), chainID),
  1017  		light.Logger(log.TestingLogger()),
  1018  		light.PruningSize(1),
  1019  	)
  1020  	require.NoError(t, err)
  1021  	_, err = c.TrustedLightBlock(1)
  1022  	require.NoError(t, err)
  1023  
  1024  	h, err := c.Update(ctx, bTime.Add(2*time.Hour))
  1025  	require.NoError(t, err)
  1026  	require.Equal(t, int64(3), h.Height)
  1027  
  1028  	_, err = c.TrustedLightBlock(1)
  1029  	assert.Error(t, err)
  1030  }
  1031  
  1032  func TestClientEnsureValidHeadersAndValSets(t *testing.T) {
  1033  	emptyValSet := &types.ValidatorSet{
  1034  		Validators: nil,
  1035  		Proposer:   nil,
  1036  	}
  1037  
  1038  	testCases := []struct {
  1039  		headers map[int64]*types.SignedHeader
  1040  		vals    map[int64]*types.ValidatorSet
  1041  		err     bool
  1042  	}{
  1043  		{
  1044  			headerSet,
  1045  			valSet,
  1046  			false,
  1047  		},
  1048  		{
  1049  			headerSet,
  1050  			map[int64]*types.ValidatorSet{
  1051  				1: vals,
  1052  				2: vals,
  1053  				3: nil,
  1054  			},
  1055  			true,
  1056  		},
  1057  		{
  1058  			map[int64]*types.SignedHeader{
  1059  				1: h1,
  1060  				2: h2,
  1061  				3: nil,
  1062  			},
  1063  			valSet,
  1064  			true,
  1065  		},
  1066  		{
  1067  			headerSet,
  1068  			map[int64]*types.ValidatorSet{
  1069  				1: vals,
  1070  				2: vals,
  1071  				3: emptyValSet,
  1072  			},
  1073  			true,
  1074  		},
  1075  	}
  1076  
  1077  	for _, tc := range testCases {
  1078  		badNode := mockp.New(
  1079  			chainID,
  1080  			tc.headers,
  1081  			tc.vals,
  1082  		)
  1083  		c, err := light.NewClient(
  1084  			ctx,
  1085  			chainID,
  1086  			trustOptions,
  1087  			badNode,
  1088  			[]provider.Provider{badNode, badNode},
  1089  			dbs.New(dbm.NewMemDB(), chainID),
  1090  			light.MaxRetryAttempts(1),
  1091  		)
  1092  		require.NoError(t, err)
  1093  
  1094  		_, err = c.VerifyLightBlockAtHeight(ctx, 3, bTime.Add(2*time.Hour))
  1095  		if tc.err {
  1096  			assert.Error(t, err)
  1097  		} else {
  1098  			assert.NoError(t, err)
  1099  		}
  1100  	}
  1101  
  1102  }
  1103  
  1104  func TestClientHandlesContexts(t *testing.T) {
  1105  	p := mockp.New(genMockNode(chainID, 100, 10, 1, bTime))
  1106  	genBlock, err := p.LightBlock(ctx, 1)
  1107  	require.NoError(t, err)
  1108  
  1109  	// instantiate the light client with a timeout
  1110  	ctxTimeOut, cancel := context.WithTimeout(ctx, 10*time.Millisecond)
  1111  	defer cancel()
  1112  	_, err = light.NewClient(
  1113  		ctxTimeOut,
  1114  		chainID,
  1115  		light.TrustOptions{
  1116  			Period: 24 * time.Hour,
  1117  			Height: 1,
  1118  			Hash:   genBlock.Hash(),
  1119  		},
  1120  		p,
  1121  		[]provider.Provider{p, p},
  1122  		dbs.New(dbm.NewMemDB(), chainID),
  1123  	)
  1124  	require.Error(t, ctxTimeOut.Err())
  1125  	require.Error(t, err)
  1126  	require.True(t, errors.Is(err, context.DeadlineExceeded))
  1127  
  1128  	// instantiate the client for real
  1129  	c, err := light.NewClient(
  1130  		ctx,
  1131  		chainID,
  1132  		light.TrustOptions{
  1133  			Period: 24 * time.Hour,
  1134  			Height: 1,
  1135  			Hash:   genBlock.Hash(),
  1136  		},
  1137  		p,
  1138  		[]provider.Provider{p, p},
  1139  		dbs.New(dbm.NewMemDB(), chainID),
  1140  	)
  1141  	require.NoError(t, err)
  1142  
  1143  	// verify a block with a timeout
  1144  	ctxTimeOutBlock, cancel := context.WithTimeout(ctx, 10*time.Millisecond)
  1145  	defer cancel()
  1146  	_, err = c.VerifyLightBlockAtHeight(ctxTimeOutBlock, 100, bTime.Add(100*time.Minute))
  1147  	require.Error(t, ctxTimeOutBlock.Err())
  1148  	require.Error(t, err)
  1149  	require.True(t, errors.Is(err, context.DeadlineExceeded))
  1150  
  1151  	// verify a block with a cancel
  1152  	ctxCancel, cancel := context.WithCancel(ctx)
  1153  	defer cancel()
  1154  	time.AfterFunc(10*time.Millisecond, cancel)
  1155  	_, err = c.VerifyLightBlockAtHeight(ctxCancel, 100, bTime.Add(100*time.Minute))
  1156  	require.Error(t, ctxCancel.Err())
  1157  	require.Error(t, err)
  1158  	require.True(t, errors.Is(err, context.Canceled))
  1159  
  1160  }