code.vegaprotocol.io/vega@v0.79.0/core/checkpoint/engine_test.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package checkpoint_test
    17  
    18  import (
    19  	"context"
    20  	"encoding/base64"
    21  	"encoding/hex"
    22  	"encoding/json"
    23  	"errors"
    24  	"testing"
    25  	"time"
    26  
    27  	"code.vegaprotocol.io/vega/core/checkpoint"
    28  	"code.vegaprotocol.io/vega/core/checkpoint/mocks"
    29  	"code.vegaprotocol.io/vega/core/types"
    30  	"code.vegaprotocol.io/vega/libs/proto"
    31  	"code.vegaprotocol.io/vega/logging"
    32  
    33  	"github.com/golang/mock/gomock"
    34  	"github.com/stretchr/testify/require"
    35  )
    36  
    37  type testEngine struct {
    38  	*checkpoint.Engine
    39  	ctrl *gomock.Controller
    40  }
    41  
    42  func getTestEngine(t *testing.T) *testEngine {
    43  	t.Helper()
    44  	ctrl := gomock.NewController(t)
    45  	log := logging.NewTestLogger()
    46  	eng, _ := checkpoint.New(log, checkpoint.NewDefaultConfig())
    47  	return &testEngine{
    48  		Engine: eng,
    49  		ctrl:   ctrl,
    50  	}
    51  }
    52  
    53  func TestGetCheckpoints(t *testing.T) {
    54  	t.Run("test getting checkpoints loading in components via constructor - no duplicates", testGetCheckpointsConstructor)
    55  	t.Run("test getting checkpoints loading in components using Add method - no duplicates", testGetCheckpointsAdd)
    56  	t.Run("test adding duplicate components using Add methods", testAddDuplicate)
    57  	t.Run("test adding duplicate component via constructor", testDuplicateConstructor)
    58  }
    59  
    60  func TestCheckpointIntervals(t *testing.T) {
    61  	t.Run("test getting checkpoint before interval has passed", testCheckpointBeforeInterval)
    62  	t.Run("test updating interval creates new checkpoint sooner", testCheckpointUpdatedInterval)
    63  	t.Run("test getting checkpoint before interval for balance", testCheckpointBalanceInterval)
    64  }
    65  
    66  func TestLoadCheckpoints(t *testing.T) {
    67  	t.Run("test loading checkpoints after generating them - success", testLoadCheckpoints)
    68  	t.Run("load non-registered components", testLoadMissingCheckpoint)
    69  	t.Run("load checkpoint with invalid hash", testLoadInvalidHash)
    70  	t.Run("load sparse checkpoint", testLoadSparse)
    71  	t.Run("error loading checkpoint", testLoadError)
    72  }
    73  
    74  func TestLoadAssets(t *testing.T) {
    75  	t.Run("test loading assets first, enables assets in collateral", testLoadAssets)
    76  }
    77  
    78  type genesis struct {
    79  	CP *checkpoint.GenesisState `json:"checkpoint"`
    80  }
    81  
    82  func testGetCheckpointsConstructor(t *testing.T) {
    83  	t.Parallel()
    84  	ctrl := gomock.NewController(t)
    85  	ctx := context.Background()
    86  	defer ctrl.Finish()
    87  	components := map[types.CheckpointName]*mocks.MockState{
    88  		types.GovernanceCheckpoint: mocks.NewMockState(ctrl),
    89  		types.AssetsCheckpoint:     mocks.NewMockState(ctrl),
    90  	}
    91  	for k, c := range components {
    92  		c.EXPECT().Name().Times(1).Return(k)
    93  	}
    94  	log := logging.NewTestLogger()
    95  	eng, err := checkpoint.New(log, checkpoint.NewDefaultConfig(), components[types.GovernanceCheckpoint], components[types.AssetsCheckpoint])
    96  	require.NoError(t, err)
    97  	data := map[types.CheckpointName][]byte{
    98  		types.GovernanceCheckpoint: []byte("foodata"),
    99  		types.AssetsCheckpoint:     []byte("bardata"),
   100  	}
   101  	for k, c := range components {
   102  		c.EXPECT().Checkpoint().Times(1).Return(data[k], nil)
   103  	}
   104  	// initialise time
   105  	tm := time.Now().Add(-2 * time.Hour)
   106  	_, _ = eng.Checkpoint(ctx, tm)
   107  	raw, err := eng.Checkpoint(ctx, time.Now())
   108  	require.NoError(t, err)
   109  	// now to check if the checkpoint contains the expected data
   110  	for k, c := range components {
   111  		c.EXPECT().Load(gomock.Any(), data[k]).Times(1).Return(nil)
   112  	}
   113  	// pretend like the genesis block specified this hash to restore
   114  	set := genesis{
   115  		CP: &checkpoint.GenesisState{
   116  			CheckpointHash:  hex.EncodeToString(raw.Hash),
   117  			CheckpointState: base64.StdEncoding.EncodeToString(raw.State),
   118  		},
   119  	}
   120  	gen, err := json.Marshal(set)
   121  	require.NoError(t, err)
   122  	require.NoError(t, eng.UponGenesis(ctx, gen))
   123  }
   124  
   125  func testGetCheckpointsAdd(t *testing.T) {
   126  	t.Parallel()
   127  	eng := getTestEngine(t)
   128  	ctx := context.Background()
   129  
   130  	components := map[types.CheckpointName]*mocks.MockState{
   131  		types.GovernanceCheckpoint: mocks.NewMockState(eng.ctrl),
   132  		types.AssetsCheckpoint:     mocks.NewMockState(eng.ctrl),
   133  	}
   134  	data := map[types.CheckpointName][]byte{
   135  		types.GovernanceCheckpoint: []byte("foodata"),
   136  		types.AssetsCheckpoint:     []byte("bardata"),
   137  	}
   138  	for k, c := range components {
   139  		c.EXPECT().Name().Times(1).Return(k)
   140  	}
   141  	require.NoError(t, eng.Add(components[types.GovernanceCheckpoint], components[types.AssetsCheckpoint]))
   142  	for k, c := range components {
   143  		c.EXPECT().Checkpoint().Times(1).Return(data[k], nil)
   144  	}
   145  	tm := time.Now().Add(-2 * time.Hour)
   146  	_, _ = eng.Checkpoint(ctx, tm)
   147  	raw, err := eng.Checkpoint(ctx, time.Now())
   148  	require.NoError(t, err)
   149  	// now to check if the checkpoint contains the expected data
   150  	for k, c := range components {
   151  		c.EXPECT().Load(gomock.Any(), data[k]).Times(1).Return(nil)
   152  	}
   153  	// pretend like the genesis block specified this hash to restore
   154  	set := genesis{
   155  		CP: &checkpoint.GenesisState{
   156  			CheckpointHash:  hex.EncodeToString(raw.Hash),
   157  			CheckpointState: base64.StdEncoding.EncodeToString(raw.State),
   158  		},
   159  	}
   160  	gen, err := json.Marshal(set)
   161  	require.NoError(t, err)
   162  	require.NoError(t, eng.UponGenesis(ctx, gen))
   163  }
   164  
   165  func testAddDuplicate(t *testing.T) {
   166  	t.Parallel()
   167  	eng := getTestEngine(t)
   168  	defer eng.ctrl.Finish()
   169  	comp := mocks.NewMockState(eng.ctrl)
   170  	comp.EXPECT().Name().Times(2).Return(types.GovernanceCheckpoint)
   171  	require.NoError(t, eng.Add(comp, comp)) // adding the exact same component (same ptr value)
   172  	comp2 := mocks.NewMockState(eng.ctrl)
   173  	comp2.EXPECT().Name().Times(1).Return(types.GovernanceCheckpoint)
   174  	require.Error(t, eng.Add(comp2))
   175  }
   176  
   177  func testDuplicateConstructor(t *testing.T) {
   178  	t.Parallel()
   179  	ctrl := gomock.NewController(t)
   180  	defer ctrl.Finish()
   181  	comp := mocks.NewMockState(ctrl)
   182  	comp.EXPECT().Name().Times(3).Return(types.GovernanceCheckpoint)
   183  	comp2 := mocks.NewMockState(ctrl)
   184  	comp2.EXPECT().Name().Times(1).Return(types.GovernanceCheckpoint)
   185  	// this is all good
   186  	log := logging.NewTestLogger()
   187  	cfg := checkpoint.NewDefaultConfig()
   188  	eng, err := checkpoint.New(log, cfg, comp, comp)
   189  	require.NoError(t, err)
   190  	require.NotNil(t, eng)
   191  	eng, err = checkpoint.New(log, cfg, comp, comp2)
   192  	require.Error(t, err)
   193  	require.Nil(t, eng)
   194  }
   195  
   196  func testLoadCheckpoints(t *testing.T) {
   197  	t.Parallel()
   198  	eng := getTestEngine(t)
   199  	ctx := context.Background()
   200  	defer eng.ctrl.Finish()
   201  	components := map[types.CheckpointName]*mocks.MockState{
   202  		types.GovernanceCheckpoint: mocks.NewMockState(eng.ctrl),
   203  		types.AssetsCheckpoint:     mocks.NewMockState(eng.ctrl),
   204  	}
   205  	data := map[types.CheckpointName][]byte{
   206  		types.GovernanceCheckpoint: []byte("foodata"),
   207  		types.AssetsCheckpoint:     []byte("bardata"),
   208  	}
   209  	for k, c := range components {
   210  		c.EXPECT().Name().Times(1).Return(k)
   211  	}
   212  	require.NoError(t, eng.Add(components[types.GovernanceCheckpoint], components[types.AssetsCheckpoint]))
   213  	for k, c := range components {
   214  		c.EXPECT().Checkpoint().Times(1).Return(data[k], nil)
   215  	}
   216  	tm := time.Now().Add(-2 * time.Hour)
   217  	_, _ = eng.Checkpoint(ctx, tm)
   218  	snapshot, err := eng.Checkpoint(ctx, time.Now())
   219  	require.NoError(t, err)
   220  	require.NotEmpty(t, snapshot)
   221  	// create new components to load data in to
   222  	wComps := map[types.CheckpointName]*wrappedMock{
   223  		types.GovernanceCheckpoint: wrapMock(mocks.NewMockState(eng.ctrl)),
   224  		types.AssetsCheckpoint:     wrapMock(mocks.NewMockState(eng.ctrl)),
   225  	}
   226  	for k, c := range wComps {
   227  		c.EXPECT().Name().Times(1).Return(k)
   228  		c.EXPECT().Load(gomock.Any(), data[k]).Times(1).Return(nil)
   229  	}
   230  	log := logging.NewTestLogger()
   231  	cfg := checkpoint.NewDefaultConfig()
   232  	newEng, err := checkpoint.New(log, cfg, wComps[types.GovernanceCheckpoint], wComps[types.AssetsCheckpoint])
   233  	require.NoError(t, err)
   234  	require.NotNil(t, newEng)
   235  	// pretend like the genesis block specified this hash to restore
   236  	set := genesis{
   237  		CP: &checkpoint.GenesisState{
   238  			CheckpointHash:  hex.EncodeToString(snapshot.Hash),
   239  			CheckpointState: base64.StdEncoding.EncodeToString(snapshot.State),
   240  		},
   241  	}
   242  	gen, err := json.Marshal(set)
   243  	require.NoError(t, err)
   244  	require.NoError(t, newEng.UponGenesis(ctx, gen))
   245  	for k, exp := range data {
   246  		wc := wComps[k]
   247  		require.EqualValues(t, exp, wc.data)
   248  	}
   249  }
   250  
   251  func testLoadMissingCheckpoint(t *testing.T) {
   252  	t.Parallel()
   253  	eng := getTestEngine(t)
   254  	ctx := context.Background()
   255  	defer eng.ctrl.Finish()
   256  
   257  	// create checkpoint data
   258  	cp := &types.Checkpoint{
   259  		Assets: []byte("assets"),
   260  	}
   261  	snap := &types.CheckpointState{}
   262  	snap.SetCheckpoint(cp)
   263  	cp.Assets = []byte("foobar")
   264  	b, err := proto.Marshal(cp.IntoProto())
   265  	require.NoError(t, err)
   266  	// pretend like the genesis block specified this hash to restore
   267  	set := genesis{
   268  		CP: &checkpoint.GenesisState{
   269  			CheckpointHash:  hex.EncodeToString(snap.Hash),
   270  			CheckpointState: base64.StdEncoding.EncodeToString(b),
   271  		},
   272  	}
   273  	gen, err := json.Marshal(set)
   274  	require.NoError(t, err)
   275  	require.Error(t, eng.UponGenesis(ctx, gen), "could not load checkpoint: received(09234807e4af85f17c66b48ee3bca89dffd1f1233659f9f940a2b17b0b8c6bc5), expected(e3795ed41024acefa48c9bdce4f52cf6909f4672dc3112fd0fc6cb1e18c83531): incompatible hashes")
   276  }
   277  
   278  func testLoadInvalidHash(t *testing.T) {
   279  	t.Parallel()
   280  	eng := getTestEngine(t)
   281  	ctx := context.Background()
   282  	defer eng.ctrl.Finish()
   283  
   284  	cp := &types.Checkpoint{
   285  		Assets: []byte("assets"),
   286  	}
   287  	snap := &types.CheckpointState{}
   288  	snap.SetCheckpoint(cp)
   289  	// update data -> hash is invalid
   290  	cp.Assets = []byte("foobar")
   291  	b, err := proto.Marshal(cp.IntoProto())
   292  	require.NoError(t, err)
   293  	// pretend like the genesis block specified this hash to restore
   294  	set := genesis{
   295  		CP: &checkpoint.GenesisState{
   296  			CheckpointHash:  hex.EncodeToString(snap.Hash),
   297  			CheckpointState: base64.StdEncoding.EncodeToString(b),
   298  		},
   299  	}
   300  	gen, err := json.Marshal(set)
   301  	require.NoError(t, err)
   302  	require.Error(t, eng.UponGenesis(ctx, gen), "could not load checkpoint: received(09234807e4af85f17c66b48ee3bca89dffd1f1233659f9f940a2b17b0b8c6bc5), expected(e3795ed41024acefa48c9bdce4f52cf6909f4672dc3112fd0fc6cb1e18c83531): incompatible hashes")
   303  }
   304  
   305  func testLoadSparse(t *testing.T) {
   306  	t.Parallel()
   307  	ctrl := gomock.NewController(t)
   308  	ctx := context.Background()
   309  	defer ctrl.Finish()
   310  	components := map[types.CheckpointName]*mocks.MockState{
   311  		types.GovernanceCheckpoint: mocks.NewMockState(ctrl),
   312  		types.AssetsCheckpoint:     mocks.NewMockState(ctrl),
   313  	}
   314  	for k, c := range components {
   315  		c.EXPECT().Name().Times(1).Return(k)
   316  	}
   317  	log := logging.NewTestLogger()
   318  	cfg := checkpoint.NewDefaultConfig()
   319  	eng, err := checkpoint.New(log, cfg, components[types.GovernanceCheckpoint])
   320  	require.NoError(t, err)
   321  	data := map[types.CheckpointName][]byte{
   322  		types.GovernanceCheckpoint: []byte("foodata"),
   323  	}
   324  	c := components[types.GovernanceCheckpoint]
   325  	c.EXPECT().Checkpoint().Times(1).Return(data[types.GovernanceCheckpoint], nil)
   326  	tm := time.Now().Add(-2 * time.Hour)
   327  	_, _ = eng.Checkpoint(ctx, tm)
   328  	snapshot, err := eng.Checkpoint(ctx, time.Now())
   329  	require.NoError(t, err)
   330  	require.NoError(t, eng.Add(components[types.AssetsCheckpoint])) // load another component, not part of the checkpoints map
   331  	c.EXPECT().Load(gomock.Any(), data[types.GovernanceCheckpoint]).Times(1).Return(nil)
   332  	// pretend like the genesis block specified this hash to restore
   333  	set := genesis{
   334  		CP: &checkpoint.GenesisState{
   335  			CheckpointHash:  hex.EncodeToString(snapshot.Hash),
   336  			CheckpointState: base64.StdEncoding.EncodeToString(snapshot.State),
   337  		},
   338  	}
   339  	gen, err := json.Marshal(set)
   340  	require.NoError(t, err)
   341  	require.NoError(t, eng.UponGenesis(ctx, gen))
   342  }
   343  
   344  func testLoadError(t *testing.T) {
   345  	t.Parallel()
   346  	ctrl := gomock.NewController(t)
   347  	ctx := context.Background()
   348  	defer ctrl.Finish()
   349  	components := map[types.CheckpointName]*mocks.MockState{
   350  		types.GovernanceCheckpoint: mocks.NewMockState(ctrl),
   351  		types.AssetsCheckpoint:     mocks.NewMockState(ctrl),
   352  	}
   353  	for k, c := range components {
   354  		c.EXPECT().Name().Times(1).Return(k)
   355  	}
   356  	log := logging.NewTestLogger()
   357  	cfg := checkpoint.NewDefaultConfig()
   358  	eng, err := checkpoint.New(log, cfg, components[types.GovernanceCheckpoint], components[types.AssetsCheckpoint])
   359  	require.NoError(t, err)
   360  	data := map[types.CheckpointName][]byte{
   361  		types.GovernanceCheckpoint: []byte("foodata"),
   362  		types.AssetsCheckpoint:     []byte("bardata"),
   363  	}
   364  	for k, c := range components {
   365  		c.EXPECT().Checkpoint().Times(1).Return(data[k], nil)
   366  	}
   367  	ret := map[types.CheckpointName]error{
   368  		types.GovernanceCheckpoint: errors.New("random error"),
   369  		types.AssetsCheckpoint:     nil, // we always load checkpoints in order, so bar will go first, and should not return an error
   370  	}
   371  	tm := time.Now().Add(-2 * time.Hour)
   372  	_, _ = eng.Checkpoint(ctx, tm)
   373  	checkpoints, err := eng.Checkpoint(ctx, time.Now())
   374  	require.NoError(t, err)
   375  	for k, r := range ret {
   376  		c := components[k]
   377  		c.EXPECT().Load(gomock.Any(), data[k]).Times(1).Return(r)
   378  	}
   379  	// pretend like the genesis block specified this hash to restore
   380  	set := genesis{
   381  		CP: &checkpoint.GenesisState{
   382  			CheckpointHash:  hex.EncodeToString(checkpoints.Hash),
   383  			CheckpointState: base64.StdEncoding.EncodeToString(checkpoints.State),
   384  		},
   385  	}
   386  	gen, err := json.Marshal(set)
   387  	require.NoError(t, err)
   388  	err = eng.UponGenesis(ctx, gen)
   389  	require.Error(t, err)
   390  	require.True(t, errors.Is(err, ret[types.GovernanceCheckpoint]))
   391  }
   392  
   393  func testCheckpointBeforeInterval(t *testing.T) {
   394  	t.Parallel()
   395  	ctrl := gomock.NewController(t)
   396  	defer ctrl.Finish()
   397  	ctx := context.Background()
   398  	components := map[types.CheckpointName]*mocks.MockState{
   399  		types.GovernanceCheckpoint: mocks.NewMockState(ctrl),
   400  		types.AssetsCheckpoint:     mocks.NewMockState(ctrl),
   401  	}
   402  	for k, c := range components {
   403  		c.EXPECT().Name().Times(1).Return(k)
   404  	}
   405  	log := logging.NewTestLogger()
   406  	cfg := checkpoint.NewDefaultConfig()
   407  	eng, err := checkpoint.New(log, cfg, components[types.GovernanceCheckpoint], components[types.AssetsCheckpoint])
   408  	require.NoError(t, err)
   409  	// set interval of 1 hour
   410  	hour, _ := time.ParseDuration("1h")
   411  	eng.OnTimeElapsedUpdate(ctx, hour)
   412  	data := map[types.CheckpointName][]byte{
   413  		types.GovernanceCheckpoint: []byte("foodata"),
   414  		types.AssetsCheckpoint:     []byte("bardata"),
   415  	}
   416  	for k, c := range components {
   417  		c.EXPECT().Checkpoint().Times(1).Return(data[k], nil)
   418  	}
   419  	tm := time.Now().Add(-2 * time.Hour)
   420  	_, _ = eng.Checkpoint(ctx, tm)
   421  	now := time.Now()
   422  	raw, err := eng.Checkpoint(ctx, now)
   423  	require.NoError(t, err)
   424  	require.NotNil(t, raw)
   425  
   426  	halfHour := time.Duration(int64(hour) / 2)
   427  	now = now.Add(halfHour)
   428  	raw, err = eng.Checkpoint(ctx, now)
   429  	require.Nil(t, raw)
   430  	require.Nil(t, err)
   431  }
   432  
   433  func testCheckpointBalanceInterval(t *testing.T) {
   434  	t.Parallel()
   435  	ctrl := gomock.NewController(t)
   436  	defer ctrl.Finish()
   437  	ctx := context.Background()
   438  	components := map[types.CheckpointName]*mocks.MockState{
   439  		types.GovernanceCheckpoint: mocks.NewMockState(ctrl),
   440  		types.AssetsCheckpoint:     mocks.NewMockState(ctrl),
   441  	}
   442  	for k, c := range components {
   443  		c.EXPECT().Name().Times(1).Return(k)
   444  	}
   445  	log := logging.NewTestLogger()
   446  	cfg := checkpoint.NewDefaultConfig()
   447  	eng, err := checkpoint.New(log, cfg, components[types.GovernanceCheckpoint], components[types.AssetsCheckpoint])
   448  	require.NoError(t, err)
   449  	// set interval of 1 hour
   450  	hour, _ := time.ParseDuration("1h")
   451  	eng.OnTimeElapsedUpdate(ctx, hour)
   452  	data := map[types.CheckpointName][]byte{
   453  		types.GovernanceCheckpoint: []byte("foodata"),
   454  		types.AssetsCheckpoint:     []byte("bardata"),
   455  	}
   456  	for k, c := range components {
   457  		c.EXPECT().Checkpoint().Times(2).Return(data[k], nil)
   458  	}
   459  	tm := time.Now().Add(-2 * time.Hour)
   460  	_, _ = eng.Checkpoint(ctx, tm)
   461  	now := time.Now()
   462  	raw, err := eng.Checkpoint(ctx, now)
   463  	require.NoError(t, err)
   464  	require.NotNil(t, raw)
   465  
   466  	halfHour := time.Duration(int64(hour) / 2)
   467  	now = now.Add(halfHour)
   468  	// progress time, but still not time to create a new checkpoint
   469  	raw, err = eng.Checkpoint(ctx, now)
   470  	require.Nil(t, raw)
   471  	require.Nil(t, err)
   472  	// for a withdrawal, though, we will create one regardless
   473  	_, err = eng.BalanceCheckpoint(ctx)
   474  	require.NoError(t, err)
   475  }
   476  
   477  // same test as above, but the interval is upadted to trigger a second checkpoint
   478  // to be created anyway.
   479  func testCheckpointUpdatedInterval(t *testing.T) {
   480  	t.Parallel()
   481  	ctrl := gomock.NewController(t)
   482  	defer ctrl.Finish()
   483  	ctx := context.Background()
   484  	components := map[types.CheckpointName]*mocks.MockState{
   485  		types.GovernanceCheckpoint: mocks.NewMockState(ctrl),
   486  		types.AssetsCheckpoint:     mocks.NewMockState(ctrl),
   487  	}
   488  	for k, c := range components {
   489  		c.EXPECT().Name().Times(1).Return(k)
   490  	}
   491  	log := logging.NewTestLogger()
   492  	cfg := checkpoint.NewDefaultConfig()
   493  	eng, err := checkpoint.New(log, cfg, components[types.GovernanceCheckpoint], components[types.AssetsCheckpoint])
   494  	require.NoError(t, err)
   495  	// set interval of 1 hour
   496  	hour, _ := time.ParseDuration("1h")
   497  	eng.OnTimeElapsedUpdate(ctx, hour)
   498  	data := map[types.CheckpointName][]byte{
   499  		types.GovernanceCheckpoint: []byte("foodata"),
   500  		types.AssetsCheckpoint:     []byte("bardata"),
   501  	}
   502  	for k, c := range components {
   503  		// we expect 2 calls
   504  		c.EXPECT().Checkpoint().Times(2).Return(data[k], nil)
   505  	}
   506  	tm := time.Now().Add(-2 * time.Hour)
   507  	_, _ = eng.Checkpoint(ctx, tm)
   508  	now := time.Now()
   509  	raw, err := eng.Checkpoint(ctx, now)
   510  	require.NoError(t, err)
   511  	require.NotNil(t, raw)
   512  
   513  	// this is before we ought to create a checkpoint, and should return nil
   514  	halfHour := time.Duration(int64(hour) / 2)
   515  	now = now.Add(halfHour)
   516  	raw, err = eng.Checkpoint(ctx, now)
   517  	require.Nil(t, raw)
   518  	require.Nil(t, err)
   519  
   520  	// now the second calls to the components are made
   521  	now = now.Add(time.Second)             // t+30m1s
   522  	eng.OnTimeElapsedUpdate(ctx, halfHour) // delta is 30 min
   523  	raw, err = eng.Checkpoint(ctx, now)
   524  	require.NoError(t, err)
   525  	require.NotNil(t, raw)
   526  }
   527  
   528  func testLoadAssets(t *testing.T) {
   529  	t.Parallel()
   530  	eng := getTestEngine(t)
   531  	ctx := context.Background()
   532  	defer eng.ctrl.Finish()
   533  	// set up mocks
   534  	data := map[types.CheckpointName][]byte{
   535  		types.GovernanceCheckpoint: []byte("foodata"),
   536  		types.AssetsCheckpoint:     []byte("bardata"),
   537  		types.CollateralCheckpoint: []byte("collateraldata"),
   538  	}
   539  	assets := mocks.NewMockAssetsState(eng.ctrl)
   540  	assets.EXPECT().Name().Times(1).Return(types.AssetsCheckpoint)
   541  	assets.EXPECT().Checkpoint().Times(1).Return(data[types.AssetsCheckpoint], nil)
   542  	collateral := mocks.NewMockCollateralState(eng.ctrl)
   543  	collateral.EXPECT().Name().Times(1).Return(types.CollateralCheckpoint)
   544  	collateral.EXPECT().Checkpoint().Times(1).Return(data[types.CollateralCheckpoint], nil)
   545  	governance := mocks.NewMockState(eng.ctrl)
   546  	governance.EXPECT().Name().Times(1).Return(types.GovernanceCheckpoint)
   547  	governance.EXPECT().Checkpoint().Times(1).Return(data[types.GovernanceCheckpoint], nil)
   548  	// add the mocks to the engine
   549  	require.NoError(t, eng.Add(governance, assets, collateral))
   550  	// get the checkpoint data
   551  	tm := time.Now().Add(-2 * time.Hour)
   552  	_, _ = eng.Checkpoint(ctx, tm)
   553  	raw, err := eng.Checkpoint(ctx, time.Now())
   554  	require.NoError(t, err)
   555  	// calling load with this checkpoint now is a noop
   556  	// require.Error(t, eng.Load(ctx, raw))
   557  	// pretend like the genesis block specified this hash to restore
   558  	set := genesis{
   559  		CP: &checkpoint.GenesisState{
   560  			CheckpointHash:  hex.EncodeToString(raw.Hash),
   561  			CheckpointState: base64.StdEncoding.EncodeToString(raw.State),
   562  		},
   563  	}
   564  
   565  	// now we do expect the calls to be made, but only once
   566  	governance.EXPECT().Load(gomock.Any(), data[types.GovernanceCheckpoint]).Times(1).Return(nil)
   567  	assets.EXPECT().Load(gomock.Any(), data[types.AssetsCheckpoint]).Times(1).Return(nil)
   568  	collateral.EXPECT().Load(gomock.Any(), data[types.CollateralCheckpoint]).Times(1).Return(nil)
   569  	// but assets ought to receive an additional call
   570  	// return this stubbed asset, we only care about the ID anyway
   571  	enabled := types.Asset{
   572  		ID: "asset",
   573  	}
   574  	assets.EXPECT().GetEnabledAssets().Times(1).Return([]*types.Asset{
   575  		&enabled,
   576  	})
   577  	collateral.EXPECT().EnableAsset(ctx, enabled).Times(1).Return(nil)
   578  
   579  	// now set the engine to accept the hash of the data we want to load
   580  	gen, err := json.Marshal(set)
   581  	require.NoError(t, err)
   582  	require.NoError(t, eng.UponGenesis(ctx, gen))
   583  }
   584  
   585  type wrappedMock struct {
   586  	*mocks.MockState
   587  	data []byte
   588  }
   589  
   590  func wrapMock(m *mocks.MockState) *wrappedMock {
   591  	return &wrappedMock{
   592  		MockState: m,
   593  	}
   594  }
   595  
   596  func (w *wrappedMock) Load(ctx context.Context, data []byte) error {
   597  	w.data = data
   598  	return w.MockState.Load(ctx, data)
   599  }