github.com/arcology-network/consensus-engine@v1.9.0/light/client_test.go (about)

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