code.vegaprotocol.io/vega@v0.79.0/core/epochtime/service_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 epochtime_test
    17  
    18  import (
    19  	"context"
    20  	"testing"
    21  	"time"
    22  
    23  	mbroker "code.vegaprotocol.io/vega/core/broker/mocks"
    24  	"code.vegaprotocol.io/vega/core/epochtime"
    25  	"code.vegaprotocol.io/vega/core/types"
    26  	"code.vegaprotocol.io/vega/logging"
    27  
    28  	"github.com/golang/mock/gomock"
    29  	"github.com/stretchr/testify/assert"
    30  	"github.com/stretchr/testify/require"
    31  )
    32  
    33  var (
    34  	epochs   []types.Epoch
    35  	restored []types.Epoch
    36  )
    37  
    38  type tstSvc struct {
    39  	*epochtime.Svc
    40  	ctrl   *gomock.Controller
    41  	broker *mbroker.MockBroker
    42  	cb     func(context.Context, time.Time)
    43  }
    44  
    45  func getEpochServiceMT(t *testing.T) *tstSvc {
    46  	t.Helper()
    47  	log := logging.NewTestLogger()
    48  	ctrl := gomock.NewController(t)
    49  	broker := mbroker.NewMockBroker(ctrl)
    50  	ret := &tstSvc{
    51  		ctrl:   ctrl,
    52  		broker: broker,
    53  	}
    54  
    55  	ret.Svc = epochtime.NewService(
    56  		log,
    57  		epochtime.NewDefaultConfig(),
    58  		broker,
    59  	)
    60  
    61  	ret.cb = ret.Svc.OnTick
    62  
    63  	_ = ret.OnEpochLengthUpdate(context.Background(), time.Hour*24) // set default epoch duration
    64  	return ret
    65  }
    66  
    67  func onEpoch(ctx context.Context, e types.Epoch) {
    68  	epochs = append(epochs, e)
    69  }
    70  
    71  func onEpochRestore(ctx context.Context, e types.Epoch) {
    72  	restored = append(epochs, e)
    73  }
    74  
    75  func TestEpochService(t *testing.T) {
    76  	now := time.Unix(0, 0).UTC()
    77  
    78  	ctx := context.Background()
    79  	service := getEpochServiceMT(t)
    80  	defer service.ctrl.Finish()
    81  
    82  	service.broker.EXPECT().Send(gomock.Any()).Times(3)
    83  
    84  	// Subscribe to epoch updates
    85  	// Reset global used in callback so that is doesn't pick up state from another test
    86  	epochs = []types.Epoch{}
    87  	service.NotifyOnEpoch(onEpoch, onEpochRestore)
    88  
    89  	// Move time forward to generate first epoch
    90  	service.cb(ctx, now)
    91  	// Check we only have one epoch update
    92  	assert.Equal(t, 1, len(epochs))
    93  	epoch := epochs[0]
    94  	// First epoch will have a 0 identifier
    95  	assert.EqualValues(t, 0, epoch.Seq)
    96  	// Start time should be the same as now
    97  	assert.Equal(t, now.String(), epoch.StartTime.String())
    98  	// Expiry time should 1 day later
    99  	nextDay := now.Add(time.Hour * 24)
   100  	assert.Equal(t, nextDay.String(), epoch.ExpireTime.String())
   101  	// End time should not be set
   102  	assert.True(t, epoch.EndTime.IsZero())
   103  
   104  	// Move time forward one day + one second to start the first block past the expiry of the first epoch
   105  	now = now.Add((time.Hour * 24) + time.Second)
   106  	service.cb(ctx, now)
   107  
   108  	// end the block to mark the end of the epoch
   109  	service.OnBlockEnd(ctx)
   110  
   111  	// start the next block to start the second epoch
   112  	service.cb(ctx, now)
   113  
   114  	// We should have 2 new updates, one for end of epoch and one for the beginning of the new one
   115  	assert.Equal(t, 3, len(epochs))
   116  	epoch = epochs[1]
   117  	assert.EqualValues(t, 0, epoch.Seq)
   118  	assert.Equal(t, now.String(), epoch.EndTime.String())
   119  
   120  	epoch = epochs[2]
   121  	assert.EqualValues(t, 1, epoch.Seq)
   122  	assert.Equal(t, now.String(), epoch.StartTime.String())
   123  	nextDay = now.Add(time.Hour * 24)
   124  	assert.Equal(t, nextDay.String(), epoch.ExpireTime.String())
   125  	assert.True(t, epoch.EndTime.IsZero())
   126  }
   127  
   128  // TestEpochServiceCheckpointLoading tests that when an epoch is loaded from checkpoint within the same epoch, the epoch is not prematurely ending right after the load.
   129  func TestEpochServiceCheckpointLoading(t *testing.T) {
   130  	now := time.Unix(0, 0).UTC()
   131  
   132  	ctx := context.Background()
   133  	service := getEpochServiceMT(t)
   134  	defer service.ctrl.Finish()
   135  
   136  	service.broker.EXPECT().Send(gomock.Any()).AnyTimes()
   137  
   138  	// Move time forward to generate first epoch
   139  	service.cb(ctx, now)
   140  
   141  	// move to 13 hours into the epoch
   142  	now = now.Add(time.Hour * 13)
   143  	service.cb(ctx, now)
   144  
   145  	// take a checkpoint - 11h to go for the epoch
   146  	cp, _ := service.Checkpoint()
   147  
   148  	loadService := getEpochServiceMT(t)
   149  	defer loadService.ctrl.Finish()
   150  	loadService.broker.EXPECT().Send(gomock.Any()).AnyTimes()
   151  
   152  	loadEpochs := []types.Epoch{}
   153  	onLoadEpoch := func(ctx context.Context, e types.Epoch) {
   154  		loadEpochs = append(loadEpochs, e)
   155  	}
   156  	loadService.NotifyOnEpoch(onLoadEpoch, onEpochRestore)
   157  
   158  	// we're loading the checkpoint 4 hours after the time it was taken but we're still within the same epoch for another few good hours
   159  	now = now.Add((time.Hour * 4))
   160  	loadService.cb(ctx, now)
   161  	loadService.Load(ctx, cp)
   162  	loadService.cb(ctx, now.Add(time.Second))
   163  	// after the load we expect an event regardless of what epoch we were in before
   164  	require.Equal(t, 2, len(loadEpochs))
   165  
   166  	// run to the expected end of the epoch and verify it's ended
   167  	now = now.Add((time.Hour * 7) + 1*time.Second)
   168  	loadService.cb(ctx, now)
   169  	require.Equal(t, 2, len(loadEpochs))
   170  
   171  	loadService.OnBlockEnd(ctx)
   172  	// add another second to start a new epoch
   173  	now = now.Add(1 * time.Second)
   174  	loadService.cb(ctx, now)
   175  	require.Equal(t, 4, len(loadEpochs))
   176  	require.Equal(t, now.String(), loadEpochs[2].EndTime.String())
   177  	require.Equal(t, now.String(), loadEpochs[3].StartTime.String())
   178  }
   179  
   180  // TestEpochServiceCheckpointFastForward tests that when an epoch is loaded from checkpoint after the epoch should have ended we're fast forwarding through the epochs that were missed.
   181  func TestEpochServiceCheckpointFastForward(t *testing.T) {
   182  	now := time.Unix(0, 0).UTC()
   183  
   184  	ctx := context.Background()
   185  	service := getEpochServiceMT(t)
   186  	defer service.ctrl.Finish()
   187  
   188  	service.broker.EXPECT().Send(gomock.Any()).AnyTimes()
   189  
   190  	// Move time forward to generate first epoch
   191  	service.cb(ctx, now)
   192  
   193  	// move to 13 hours into the epoch
   194  	now = now.Add(time.Hour * 13)
   195  	service.cb(ctx, now)
   196  
   197  	// take a checkpoint - 11h to go for the epoch
   198  	// this epoch started at midnight and ends at next midnight
   199  	cp, _ := service.Checkpoint()
   200  
   201  	loadService := getEpochServiceMT(t)
   202  	defer loadService.ctrl.Finish()
   203  	loadService.broker.EXPECT().Send(gomock.Any()).AnyTimes()
   204  
   205  	loadEpochs := []types.Epoch{}
   206  	onLoadEpoch := func(ctx context.Context, e types.Epoch) {
   207  		loadEpochs = append(loadEpochs, e)
   208  	}
   209  	loadService.NotifyOnEpoch(onLoadEpoch, onEpochRestore)
   210  
   211  	// we're loading the checkpoint 4 hours after the time it was taken - so we expect the first epoch (1) to have been finished, as well as epoch 2 and 3 and epoch 4 started
   212  	// 72h means:
   213  	// finished epoch 0 at 2/1 midnight + 1 seconds
   214  	// started epoch 1 at 2/1 midnight + 1 seconds
   215  	// finished epoch 1 at 3/1 midnight + 2 seconds
   216  	// started epoch 2 at 3/1 midnight + 2 seconds
   217  	// ended epoch 2 at 4/1 midnight + 3 seconds
   218  	// started epoch 3 at  4/1 midnight + 3 seconds
   219  	// we're at 13h in epoch 3
   220  	now = now.Add(time.Hour * 72)
   221  	loadService.cb(ctx, now)
   222  	loadService.Load(ctx, cp)
   223  
   224  	// new block should trigger fast forward
   225  	loadService.cb(ctx, now.Add(time.Second))
   226  	require.Equal(t, 8, len(loadEpochs))
   227  
   228  	// to advance to the first block after the expiry we need advance by for 11h and 4 seconds
   229  	now = now.Add((time.Hour * 11) + 4*time.Second)
   230  	loadService.cb(ctx, now)
   231  	loadService.OnBlockEnd(ctx)
   232  
   233  	// add another second to start a new epoch
   234  	now = now.Add(1 * time.Second)
   235  	loadService.cb(ctx, now)
   236  	require.Equal(t, 10, len(loadEpochs))
   237  
   238  	require.Equal(t, uint64(0), loadEpochs[2].Seq)
   239  	require.Equal(t, "1970-01-01 00:00:00 +0000 UTC", loadEpochs[2].StartTime.UTC().String())
   240  	require.Equal(t, "1970-01-02 00:00:01 +0000 UTC", loadEpochs[2].EndTime.UTC().String())
   241  	require.Equal(t, uint64(1), loadEpochs[3].Seq)
   242  	require.Equal(t, "1970-01-02 00:00:01 +0000 UTC", loadEpochs[3].StartTime.UTC().String())
   243  	require.Equal(t, "1970-01-03 00:00:01 +0000 UTC", loadEpochs[3].ExpireTime.UTC().String())
   244  	require.Equal(t, uint64(1), loadEpochs[4].Seq)
   245  	require.Equal(t, "1970-01-02 00:00:01 +0000 UTC", loadEpochs[4].StartTime.UTC().String())
   246  	require.Equal(t, "1970-01-03 00:00:02 +0000 UTC", loadEpochs[4].EndTime.UTC().String())
   247  	require.Equal(t, uint64(2), loadEpochs[5].Seq)
   248  	require.Equal(t, "1970-01-03 00:00:02 +0000 UTC", loadEpochs[5].StartTime.UTC().String())
   249  	require.Equal(t, "1970-01-04 00:00:02 +0000 UTC", loadEpochs[5].ExpireTime.UTC().String())
   250  	require.Equal(t, uint64(2), loadEpochs[6].Seq)
   251  	require.Equal(t, "1970-01-03 00:00:02 +0000 UTC", loadEpochs[6].StartTime.UTC().String())
   252  	require.Equal(t, "1970-01-04 00:00:03 +0000 UTC", loadEpochs[6].EndTime.UTC().String())
   253  	require.Equal(t, uint64(3), loadEpochs[7].Seq)
   254  	require.Equal(t, "1970-01-04 00:00:03 +0000 UTC", loadEpochs[7].StartTime.UTC().String())
   255  	require.Equal(t, "1970-01-05 00:00:03 +0000 UTC", loadEpochs[7].ExpireTime.UTC().String())
   256  	require.Equal(t, uint64(3), loadEpochs[8].Seq)
   257  	require.Equal(t, "1970-01-04 00:00:03 +0000 UTC", loadEpochs[8].StartTime.UTC().String())
   258  	require.Equal(t, "1970-01-05 00:00:05 +0000 UTC", loadEpochs[8].EndTime.UTC().String())
   259  	require.Equal(t, uint64(4), loadEpochs[9].Seq)
   260  	require.Equal(t, "1970-01-05 00:00:05 +0000 UTC", loadEpochs[9].StartTime.UTC().String())
   261  }