code.vegaprotocol.io/vega@v0.79.0/datanode/candlesv2/candle_updates_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 candlesv2_test
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"sync"
    22  	"testing"
    23  	"time"
    24  
    25  	"code.vegaprotocol.io/vega/datanode/candlesv2"
    26  	"code.vegaprotocol.io/vega/datanode/config/encoding"
    27  	"code.vegaprotocol.io/vega/datanode/entities"
    28  	"code.vegaprotocol.io/vega/logging"
    29  
    30  	"github.com/shopspring/decimal"
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/require"
    33  )
    34  
    35  type nonReturningCandleSource struct{}
    36  
    37  func (t *nonReturningCandleSource) GetCandleDataForTimeSpan(ctx context.Context, candleID string, from *time.Time, to *time.Time,
    38  	p entities.CursorPagination,
    39  ) ([]entities.Candle, entities.PageInfo, error) {
    40  	for {
    41  		time.Sleep(1 * time.Second)
    42  	}
    43  }
    44  
    45  type errorsAlwaysCandleSource struct{}
    46  
    47  func (t *errorsAlwaysCandleSource) GetCandleDataForTimeSpan(ctx context.Context, candleID string, from *time.Time, to *time.Time,
    48  	p entities.CursorPagination,
    49  ) ([]entities.Candle, entities.PageInfo, error) {
    50  	return nil, entities.PageInfo{}, fmt.Errorf("always errors")
    51  }
    52  
    53  type testCandleSource struct {
    54  	candles chan []entities.Candle
    55  	errorCh chan error
    56  }
    57  
    58  func (t *testCandleSource) GetCandleDataForTimeSpan(ctx context.Context, candleID string, from *time.Time, to *time.Time,
    59  	p entities.CursorPagination,
    60  ) ([]entities.Candle, entities.PageInfo, error) {
    61  	pageInfo := entities.PageInfo{}
    62  	select {
    63  	case c := <-t.candles:
    64  		return c, pageInfo, nil
    65  	case err := <-t.errorCh:
    66  		return nil, entities.PageInfo{}, err
    67  	default:
    68  		return nil, pageInfo, nil
    69  	}
    70  }
    71  
    72  func TestSubscribeAndUnsubscribeCloseChannelPanic(t *testing.T) {
    73  	testCandleSource := &testCandleSource{candles: make(chan []entities.Candle, 3), errorCh: make(chan error)}
    74  	// ensure the sub channels are buffered
    75  	updates := candlesv2.NewCandleUpdates(context.Background(), logging.NewTestLogger(), "testCandles",
    76  		testCandleSource, newTestCandleConfig(5).CandleUpdates)
    77  	startTime := time.Now()
    78  
    79  	updated := startTime
    80  	firstCandle := createCandle(startTime, updated, 1, 1, 1, 1, 10, 200)
    81  	lastCandle := firstCandle // just for the sake of types
    82  	fCh := make(chan struct{})
    83  	wg := sync.WaitGroup{}
    84  	wg.Add(1)
    85  	go func() {
    86  		defer wg.Done()
    87  		testCandleSource.candles <- []entities.Candle{firstCandle}
    88  		close(fCh)
    89  		// keep updating the most recent candle
    90  		for i := 0; i < 3; i++ {
    91  			updated = updated.Add(time.Second * time.Duration(i))
    92  			lastCandle = createCandle(startTime, updated, 1, 1, 1, 1, 10, 200)
    93  			testCandleSource.candles <- []entities.Candle{lastCandle}
    94  		}
    95  	}()
    96  	<-fCh
    97  	// ensure the first candle is sent
    98  	sub1Id, out1, _ := updates.Subscribe()
    99  	sub2Id, out2, _ := updates.Subscribe()
   100  
   101  	candle1 := <-out1
   102  	assert.Equal(t, firstCandle, candle1)
   103  
   104  	candle2 := <-out2
   105  	assert.Equal(t, firstCandle, candle2)
   106  
   107  	// unsubscribe the first subscriber
   108  	updates.Unsubscribe(sub1Id)
   109  	// now wait for the updates:
   110  	wg.Wait()
   111  	sub3Id, out3, _ := updates.Subscribe()
   112  	candle3 := <-out3
   113  	require.Equal(t, lastCandle, candle3)
   114  	// this should unsubscribe sub2 already
   115  	testCandleSource.errorCh <- fmt.Errorf("transient error")
   116  
   117  	// this sub should get instantly unsubscribed.
   118  	errSub, eOut, _ := updates.Subscribe()
   119  	require.NotNil(t, eOut)
   120  	// reading from the channel should indicate it was closed already due to the error
   121  	// once the channel is closed, the subscriber effectively has to have been removed.
   122  	_, closed := <-eOut
   123  	require.True(t, closed)
   124  	// we can still safely call unsubscribe, though:
   125  	updates.Unsubscribe(errSub)
   126  	updates.Unsubscribe(sub2Id)
   127  	updates.Unsubscribe(sub3Id)
   128  }
   129  
   130  func TestSubscribeAndUnsubscribeWhenCandleSourceErrorsAlways(t *testing.T) {
   131  	errorsAlwaysCandleSource := &errorsAlwaysCandleSource{}
   132  
   133  	updates := candlesv2.NewCandleUpdates(context.Background(), logging.NewTestLogger(), "testCandles",
   134  		errorsAlwaysCandleSource, newTestCandleConfig(0).CandleUpdates)
   135  
   136  	sub1Id, _, err1 := updates.Subscribe()
   137  	sub2Id, _, err2 := updates.Subscribe()
   138  	require.NoError(t, err1)
   139  	require.NoError(t, err2)
   140  
   141  	updates.Unsubscribe(sub1Id)
   142  	updates.Unsubscribe(sub2Id)
   143  }
   144  
   145  func TestUnsubscribeAfterTransientFailure(t *testing.T) {
   146  	testCandleSource := &testCandleSource{candles: make(chan []entities.Candle), errorCh: make(chan error)}
   147  
   148  	updates := candlesv2.NewCandleUpdates(context.Background(), logging.NewTestLogger(), "testCandles",
   149  		testCandleSource, newTestCandleConfig(0).CandleUpdates)
   150  	startTime := time.Now()
   151  
   152  	sub1Id, out1, _ := updates.Subscribe()
   153  	sub2Id, out2, _ := updates.Subscribe()
   154  
   155  	firstCandle := createCandle(startTime, startTime, 1, 1, 1, 1, 10, 200)
   156  	testCandleSource.candles <- []entities.Candle{firstCandle}
   157  
   158  	candle1 := <-out1
   159  	assert.Equal(t, firstCandle, candle1)
   160  
   161  	candle2 := <-out2
   162  	assert.Equal(t, firstCandle, candle2)
   163  
   164  	testCandleSource.errorCh <- fmt.Errorf("transient error")
   165  
   166  	updates.Unsubscribe(sub1Id)
   167  	updates.Unsubscribe(sub2Id)
   168  }
   169  
   170  func TestSubscribeAfterTransientFailure(t *testing.T) {
   171  	testCandleSource := &testCandleSource{candles: make(chan []entities.Candle), errorCh: make(chan error)}
   172  
   173  	updates := candlesv2.NewCandleUpdates(context.Background(), logging.NewTestLogger(), "testCandles",
   174  		testCandleSource, newTestCandleConfig(0).CandleUpdates)
   175  	startTime := time.Now()
   176  
   177  	_, out1, _ := updates.Subscribe()
   178  	_, out2, _ := updates.Subscribe()
   179  
   180  	firstCandle := createCandle(startTime, startTime, 1, 1, 1, 1, 10, 100)
   181  	testCandleSource.candles <- []entities.Candle{firstCandle}
   182  
   183  	candle1 := <-out1
   184  	assert.Equal(t, firstCandle, candle1)
   185  
   186  	candle2 := <-out2
   187  	assert.Equal(t, firstCandle, candle2)
   188  
   189  	testCandleSource.errorCh <- fmt.Errorf("transient error")
   190  
   191  	_, out3, _ := updates.Subscribe()
   192  
   193  	candle3 := <-out3
   194  	assert.Equal(t, firstCandle, candle3)
   195  
   196  	secondCandle := createCandle(startTime.Add(1*time.Minute), startTime.Add(1*time.Minute), 2, 2, 2, 2, 20, 100)
   197  	testCandleSource.candles <- []entities.Candle{secondCandle}
   198  
   199  	candle1 = <-out1
   200  	assert.Equal(t, secondCandle, candle1)
   201  
   202  	candle2 = <-out2
   203  	assert.Equal(t, secondCandle, candle2)
   204  
   205  	candle3 = <-out3
   206  	assert.Equal(t, secondCandle, candle3)
   207  }
   208  
   209  func TestSubscribe(t *testing.T) {
   210  	testCandleSource := &testCandleSource{candles: make(chan []entities.Candle)}
   211  
   212  	updates := candlesv2.NewCandleUpdates(context.Background(), logging.NewTestLogger(), "testCandles",
   213  		testCandleSource, newTestCandleConfig(0).CandleUpdates)
   214  	startTime := time.Now()
   215  
   216  	_, out1, _ := updates.Subscribe()
   217  	_, out2, _ := updates.Subscribe()
   218  
   219  	expectedCandle := createCandle(startTime, startTime, 1, 1, 1, 1, 10, 100)
   220  	testCandleSource.candles <- []entities.Candle{expectedCandle}
   221  
   222  	candle1 := <-out1
   223  	assert.Equal(t, expectedCandle, candle1)
   224  
   225  	candle2 := <-out2
   226  	assert.Equal(t, expectedCandle, candle2)
   227  
   228  	expectedCandle = createCandle(startTime.Add(1*time.Minute), startTime.Add(1*time.Minute), 2, 2, 2, 2, 20, 100)
   229  	testCandleSource.candles <- []entities.Candle{expectedCandle}
   230  
   231  	candle1 = <-out1
   232  	assert.Equal(t, expectedCandle, candle1)
   233  
   234  	candle2 = <-out2
   235  	assert.Equal(t, expectedCandle, candle2)
   236  }
   237  
   238  func TestUnsubscribe(t *testing.T) {
   239  	testCandleSource := &testCandleSource{candles: make(chan []entities.Candle)}
   240  
   241  	updates := candlesv2.NewCandleUpdates(context.Background(), logging.NewTestLogger(), "testCandles",
   242  		testCandleSource, newTestCandleConfig(0).CandleUpdates)
   243  	startTime := time.Now()
   244  
   245  	id, out1, _ := updates.Subscribe()
   246  
   247  	expectedCandle := createCandle(startTime, startTime, 1, 1, 1, 1, 10, 100)
   248  	testCandleSource.candles <- []entities.Candle{expectedCandle}
   249  
   250  	candle1 := <-out1
   251  	assert.Equal(t, expectedCandle, candle1)
   252  
   253  	updates.Unsubscribe(id)
   254  
   255  	_, ok := <-out1
   256  	assert.False(t, ok, "candle should be closed")
   257  }
   258  
   259  func TestNewSubscriberAlwaysGetsLastCandle(t *testing.T) {
   260  	testCandleSource := &testCandleSource{candles: make(chan []entities.Candle)}
   261  
   262  	updates := candlesv2.NewCandleUpdates(context.Background(), logging.NewTestLogger(), "testCandles",
   263  		testCandleSource, newTestCandleConfig(0).CandleUpdates)
   264  	startTime := time.Now()
   265  
   266  	_, out1, _ := updates.Subscribe()
   267  
   268  	expectedCandle := createCandle(startTime, startTime, 1, 1, 1, 1, 10, 100)
   269  	testCandleSource.candles <- []entities.Candle{expectedCandle}
   270  
   271  	candle1 := <-out1
   272  	assert.Equal(t, expectedCandle, candle1)
   273  
   274  	_, out2, _ := updates.Subscribe()
   275  	candle2 := <-out2
   276  	assert.Equal(t, expectedCandle, candle2)
   277  }
   278  
   279  func TestSubscribeWithNonZeroSubscribeBuffer(t *testing.T) {
   280  	testCandleSource := &testCandleSource{candles: make(chan []entities.Candle)}
   281  
   282  	updates := candlesv2.NewCandleUpdates(context.Background(), logging.NewTestLogger(), "testCandles",
   283  		testCandleSource, newTestCandleConfig(100).CandleUpdates)
   284  	startTime := time.Now()
   285  
   286  	_, out1, _ := updates.Subscribe()
   287  	_, out2, _ := updates.Subscribe()
   288  
   289  	expectedCandle := createCandle(startTime, startTime, 1, 1, 1, 1, 10, 100)
   290  	testCandleSource.candles <- []entities.Candle{expectedCandle}
   291  
   292  	candle1 := <-out1
   293  	assert.Equal(t, expectedCandle, candle1)
   294  
   295  	candle2 := <-out2
   296  	assert.Equal(t, expectedCandle, candle2)
   297  
   298  	expectedCandle = createCandle(startTime.Add(1*time.Minute), startTime.Add(1*time.Minute), 2, 2, 2, 2, 20, 100)
   299  	testCandleSource.candles <- []entities.Candle{expectedCandle}
   300  
   301  	candle1 = <-out1
   302  	assert.Equal(t, expectedCandle, candle1)
   303  
   304  	candle2 = <-out2
   305  	assert.Equal(t, expectedCandle, candle2)
   306  }
   307  
   308  func TestUnsubscribeWithNonZeroSubscribeBuffer(t *testing.T) {
   309  	testCandleSource := &testCandleSource{candles: make(chan []entities.Candle)}
   310  
   311  	updates := candlesv2.NewCandleUpdates(context.Background(), logging.NewTestLogger(), "testCandles",
   312  		testCandleSource, newTestCandleConfig(100).CandleUpdates)
   313  	startTime := time.Now()
   314  
   315  	id, out1, _ := updates.Subscribe()
   316  
   317  	expectedCandle := createCandle(startTime, startTime, 1, 1, 1, 1, 10, 100)
   318  	testCandleSource.candles <- []entities.Candle{expectedCandle}
   319  
   320  	candle1 := <-out1
   321  	assert.Equal(t, expectedCandle, candle1)
   322  
   323  	updates.Unsubscribe(id)
   324  
   325  	_, ok := <-out1
   326  	assert.False(t, ok, "candle should be closed")
   327  }
   328  
   329  func TestSubscribeAndUnSubscribeWithNonReturningSource(t *testing.T) {
   330  	testCandleSource := &nonReturningCandleSource{}
   331  
   332  	updates := candlesv2.NewCandleUpdates(context.Background(), logging.NewTestLogger(), "testCandles",
   333  		testCandleSource, newTestCandleConfig(100).CandleUpdates)
   334  
   335  	subID1, _, _ := updates.Subscribe()
   336  	subID2, _, _ := updates.Subscribe()
   337  
   338  	updates.Unsubscribe(subID1)
   339  	updates.Unsubscribe(subID2)
   340  }
   341  
   342  func TestMultipleSlowConsumers(t *testing.T) {
   343  	nSends := 100
   344  	testCandleSource := &testCandleSource{candles: make(chan []entities.Candle, nSends), errorCh: make(chan error)}
   345  	// ensure the sub channels are buffered
   346  	updates := candlesv2.NewCandleUpdates(context.Background(), logging.NewTestLogger(), "testCandles",
   347  		testCandleSource, newTestCandleConfig(5).CandleUpdates)
   348  	startTime := time.Now()
   349  
   350  	updated := startTime
   351  	firstCandle := createCandle(startTime, updated, 1, 1, 1, 1, 10, 200)
   352  	lastCandle := firstCandle // just for the sake of types
   353  
   354  	wg := sync.WaitGroup{}
   355  	wg.Add(1)
   356  	go func() {
   357  		defer wg.Done()
   358  		testCandleSource.candles <- []entities.Candle{firstCandle}
   359  
   360  		// keep updating the most recent candle
   361  		for i := 0; i < nSends; i++ {
   362  			updated = updated.Add(time.Second * time.Duration(i))
   363  			lastCandle = createCandle(startTime, updated, 1, 1, 1, 1, 10, 200)
   364  			testCandleSource.candles <- []entities.Candle{lastCandle, lastCandle, lastCandle}
   365  		}
   366  	}()
   367  	// ensure the first candle is sent
   368  	_, _, _ = updates.Subscribe()
   369  	_, _, _ = updates.Subscribe()
   370  	wg.Wait()
   371  }
   372  
   373  func newTestCandleConfig(subscribeBufferSize int) candlesv2.Config {
   374  	conf := candlesv2.NewDefaultConfig()
   375  	conf.CandleUpdates = candlesv2.CandleUpdatesConfig{
   376  		CandleUpdatesStreamBufferSize:                1,
   377  		CandleUpdatesStreamInterval:                  encoding.Duration{Duration: 1 * time.Microsecond},
   378  		CandlesFetchTimeout:                          encoding.Duration{Duration: 2 * time.Minute},
   379  		CandleUpdatesStreamSubscriptionMsgBufferSize: subscribeBufferSize,
   380  	}
   381  
   382  	return conf
   383  }
   384  
   385  func createCandle(periodStart time.Time, lastUpdate time.Time, open int, close int, high int, low int, volume, notional int) entities.Candle {
   386  	return entities.Candle{
   387  		PeriodStart:        periodStart,
   388  		LastUpdateInPeriod: lastUpdate,
   389  		Open:               decimal.NewFromInt(int64(open)),
   390  		Close:              decimal.NewFromInt(int64(close)),
   391  		High:               decimal.NewFromInt(int64(high)),
   392  		Low:                decimal.NewFromInt(int64(low)),
   393  		Volume:             uint64(volume),
   394  		Notional:           uint64(notional),
   395  	}
   396  }