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