github.com/klaytn/klaytn@v1.10.2/datasync/chaindatafetcher/chaindata_fetcher_test.go (about)

     1  // Copyright 2020 The klaytn Authors
     2  // This file is part of the klaytn library.
     3  //
     4  // The klaytn library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The klaytn library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the klaytn library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package chaindatafetcher
    18  
    19  import (
    20  	"errors"
    21  	"math/big"
    22  	"strings"
    23  	"sync"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/golang/mock/gomock"
    28  	"github.com/klaytn/klaytn/blockchain"
    29  	"github.com/klaytn/klaytn/blockchain/types"
    30  	"github.com/klaytn/klaytn/common"
    31  	"github.com/klaytn/klaytn/datasync/chaindatafetcher/mocks"
    32  	cfTypes "github.com/klaytn/klaytn/datasync/chaindatafetcher/types"
    33  	eventMocks "github.com/klaytn/klaytn/event/mocks"
    34  	"github.com/klaytn/klaytn/log"
    35  	"github.com/stretchr/testify/assert"
    36  )
    37  
    38  func newTestChainDataFetcher() *ChainDataFetcher {
    39  	return &ChainDataFetcher{
    40  		config:                DefaultChainDataFetcherConfig(),
    41  		chainCh:               make(chan blockchain.ChainEvent),
    42  		reqCh:                 make(chan *cfTypes.Request),
    43  		stopCh:                make(chan struct{}),
    44  		numHandlers:           3,
    45  		checkpoint:            0,
    46  		checkpointMap:         make(map[int64]struct{}),
    47  		repo:                  nil,
    48  		maxProcessingDataSize: common.StorageSize(DefaultMaxProcessingDataSize * 1024 * 1024),
    49  	}
    50  }
    51  
    52  func TestChainDataFetcher_Success_StartAndStop(t *testing.T) {
    53  	fetcher := newTestChainDataFetcher()
    54  	fetcher.config.NoDefaultStart = true
    55  	// Start launches several goroutines.
    56  	assert.NoError(t, fetcher.Start(nil))
    57  
    58  	// Stop waits the all goroutines to be terminated.
    59  	assert.NoError(t, fetcher.Stop())
    60  }
    61  
    62  func TestChainDataFetcher_Success_sendRequests(t *testing.T) {
    63  	fetcher := newTestChainDataFetcher()
    64  	stopCh := make(chan struct{})
    65  
    66  	startBlock := uint64(0)
    67  	endBlock := uint64(10)
    68  
    69  	wg := sync.WaitGroup{}
    70  	wg.Add(1)
    71  	go func() {
    72  		defer wg.Done()
    73  		fetcher.sendRequests(startBlock, endBlock, cfTypes.RequestTypeAll, false, stopCh)
    74  	}()
    75  
    76  	// take all the items from the reqCh and check them.
    77  	for i := startBlock; i <= endBlock; i++ {
    78  		r := <-fetcher.reqCh
    79  		assert.Equal(t, i, r.BlockNumber)
    80  		assert.Equal(t, cfTypes.RequestTypeAll, r.ReqType)
    81  		assert.Equal(t, false, r.ShouldUpdateCheckpoint)
    82  	}
    83  	wg.Wait()
    84  }
    85  
    86  func TestChainDataFetcher_Success_sendRequestsStop(t *testing.T) {
    87  	fetcher := newTestChainDataFetcher()
    88  	stopCh := make(chan struct{})
    89  
    90  	startBlock := uint64(0)
    91  	endBlock := uint64(10)
    92  
    93  	wg := sync.WaitGroup{}
    94  	wg.Add(1)
    95  	go func() {
    96  		defer wg.Done()
    97  		fetcher.sendRequests(startBlock, endBlock, cfTypes.RequestTypeAll, false, stopCh)
    98  	}()
    99  
   100  	stopCh <- struct{}{}
   101  	wg.Wait()
   102  }
   103  
   104  func TestChainDataFetcher_Success_fetchingStartAndStop(t *testing.T) {
   105  	fetcher := newTestChainDataFetcher()
   106  
   107  	ctrl := gomock.NewController(t)
   108  	defer ctrl.Finish()
   109  
   110  	bc := mocks.NewMockBlockChain(ctrl)
   111  	bc.EXPECT().SubscribeChainEvent(gomock.Any()).Return(nil).Times(1)
   112  	// TODO-ChainDataFetcher the below statement is not working, so find out why.
   113  	// bc.EXPECT().SubscribeChainEvent(gomock.Eq(fetcher.chainCh)).Return(nil).Times(1)
   114  	bc.EXPECT().CurrentHeader().Return(&types.Header{Number: big.NewInt(1)}).Times(1)
   115  
   116  	fetcher.blockchain = bc
   117  	assert.NoError(t, fetcher.startFetching())
   118  
   119  	sub := eventMocks.NewMockSubscription(ctrl)
   120  	sub.EXPECT().Unsubscribe().Times(1)
   121  	fetcher.chainSub = sub
   122  	assert.NoError(t, fetcher.stopFetching())
   123  }
   124  
   125  func TestChainDataFetcher_Success_rangeFetchingStartAndStop(t *testing.T) {
   126  	fetcher := newTestChainDataFetcher()
   127  	// start range fetching and the method is waiting for the items to be taken from reqCh
   128  	assert.NoError(t, fetcher.startRangeFetching(0, 10, cfTypes.RequestTypeAll))
   129  
   130  	// take only parts of the requests
   131  	<-fetcher.reqCh
   132  	<-fetcher.reqCh
   133  	<-fetcher.reqCh
   134  
   135  	// stop fetching while waiting
   136  	assert.NoError(t, fetcher.stopRangeFetching())
   137  }
   138  
   139  func TestChainDataFetcher_Success_rangeFetchingStartAndFinishedAlready(t *testing.T) {
   140  	fetcher := newTestChainDataFetcher()
   141  	assert.NoError(t, fetcher.startRangeFetching(0, 0, cfTypes.RequestTypeAll))
   142  	// skip the request
   143  	<-fetcher.reqCh
   144  
   145  	// sleep to finish sending the request
   146  	time.Sleep(100 * time.Millisecond)
   147  
   148  	// already finished range fetching
   149  	err := fetcher.stopRangeFetching()
   150  	assert.Error(t, err)
   151  	assert.True(t, strings.Contains(err.Error(), "range fetching is not running"))
   152  }
   153  
   154  func TestChainDataFetcher_Success_rangeFetchingThrottling(t *testing.T) {
   155  	log.EnableLogForTest(log.LvlCrit, log.LvlTrace)
   156  
   157  	fetcher := newTestChainDataFetcher()
   158  	fetcher.processingDataSize = fetcher.maxProcessingDataSize + common.StorageSize(100*1024*1024) // assume that the processing data size is bigger than max
   159  
   160  	assert.NoError(t, fetcher.startRangeFetching(0, 10, cfTypes.RequestTypeGroupAll))
   161  	update := make(chan struct{})
   162  
   163  	go func() {
   164  		time.Sleep(1 * time.Second)
   165  		update <- struct{}{}
   166  	}()
   167  
   168  	select {
   169  	case <-fetcher.reqCh:
   170  		t.Fatal("failed throttling")
   171  	case <-update:
   172  		t.Log("working throttling")
   173  	}
   174  
   175  	// update data processing size
   176  	fetcher.dataSizeLocker.Lock()
   177  	fetcher.processingDataSize = 0
   178  	fetcher.dataSizeLocker.Unlock()
   179  
   180  	select {
   181  	case <-fetcher.reqCh:
   182  		t.Log("working after updating processing data size")
   183  	case <-time.NewTimer(1 * time.Second).C:
   184  		t.Fatal("failed update throttling")
   185  	}
   186  
   187  	// stop fetching while waiting
   188  	assert.NoError(t, fetcher.stopRangeFetching())
   189  }
   190  
   191  func TestChainDataFetcher_updateCheckpoint(t *testing.T) {
   192  	ctrl := gomock.NewController(t)
   193  	defer ctrl.Finish()
   194  
   195  	m := mocks.NewMockCheckpointDB(ctrl)
   196  
   197  	fetcher := &ChainDataFetcher{
   198  		checkpoint:    0,
   199  		checkpointMap: make(map[int64]struct{}),
   200  		checkpointDB:  m,
   201  	}
   202  
   203  	// update checkpoint as follows.
   204  	// done order: 1, 0, 2, 3, 5, 7, 9, 8, 4, 6, 10
   205  	// checkpoint: 0, 2, 3, 4, 4, 4, 4, 4, 6, 10, 11
   206  	assert.NoError(t, fetcher.updateCheckpoint(1))
   207  
   208  	m.EXPECT().WriteCheckpoint(gomock.Eq(int64(2)))
   209  	assert.NoError(t, fetcher.updateCheckpoint(0))
   210  
   211  	m.EXPECT().WriteCheckpoint(gomock.Eq(int64(3)))
   212  	assert.NoError(t, fetcher.updateCheckpoint(2))
   213  
   214  	m.EXPECT().WriteCheckpoint(gomock.Eq(int64(4)))
   215  	assert.NoError(t, fetcher.updateCheckpoint(3))
   216  	assert.NoError(t, fetcher.updateCheckpoint(5))
   217  	assert.NoError(t, fetcher.updateCheckpoint(7))
   218  	assert.NoError(t, fetcher.updateCheckpoint(9))
   219  	assert.NoError(t, fetcher.updateCheckpoint(8))
   220  
   221  	m.EXPECT().WriteCheckpoint(gomock.Eq(int64(6)))
   222  	assert.NoError(t, fetcher.updateCheckpoint(4))
   223  
   224  	m.EXPECT().WriteCheckpoint(gomock.Eq(int64(10)))
   225  	assert.NoError(t, fetcher.updateCheckpoint(6))
   226  
   227  	m.EXPECT().WriteCheckpoint(gomock.Eq(int64(11)))
   228  	assert.NoError(t, fetcher.updateCheckpoint(10))
   229  }
   230  
   231  func TestChainDataFetcher_retryFunc(t *testing.T) {
   232  	fetcher := &ChainDataFetcher{}
   233  	header := &types.Header{
   234  		Number: big.NewInt(1),
   235  	}
   236  	ev := blockchain.ChainEvent{
   237  		Block: types.NewBlockWithHeader(header),
   238  	}
   239  
   240  	i := 0
   241  	f := func(event blockchain.ChainEvent, reqType cfTypes.RequestType) error {
   242  		i++
   243  		if i == 5 {
   244  			return nil
   245  		} else {
   246  			return errors.New("test")
   247  		}
   248  	}
   249  
   250  	assert.NoError(t, fetcher.retryFunc(f)(ev, cfTypes.RequestTypeAll))
   251  	assert.True(t, i == 5)
   252  }
   253  
   254  func TestChainDataFetcher_handleRequestByType_WhileRetrying(t *testing.T) {
   255  	fetcher := &ChainDataFetcher{
   256  		config:        &ChainDataFetcherConfig{NumHandlers: 1}, // prevent panic with nil reference
   257  		checkpoint:    1,
   258  		checkpointMap: make(map[int64]struct{}), // in order to call CheckpointDB WriteCheckpoint method
   259  		stopCh:        make(chan struct{}),      // in order to stop retrying
   260  	}
   261  	header1 := &types.Header{Number: big.NewInt(1)} // next block to be handled is 1
   262  	block1 := blockchain.ChainEvent{Block: types.NewBlockWithHeader(header1)}
   263  	header2 := &types.Header{Number: big.NewInt(2)} // next block to be handled is 2
   264  	block2 := blockchain.ChainEvent{Block: types.NewBlockWithHeader(header2)}
   265  	testError := errors.New("test-error") // fake error to call retrying infinitely
   266  
   267  	// set up mocks
   268  	ctrl := gomock.NewController(t)
   269  	defer ctrl.Finish()
   270  	mockRepo, checkpointDB := mocks.NewMockRepository(ctrl), mocks.NewMockCheckpointDB(ctrl)
   271  
   272  	// update checkpoint to 2
   273  	precall := mockRepo.EXPECT().HandleChainEvent(gomock.Any(), gomock.Any()).Return(nil).Times(6)
   274  	checkpointDB.EXPECT().WriteCheckpoint(gomock.Eq(int64(2))).Return(nil).Times(1)
   275  
   276  	// retrying indefinitely
   277  	mockRepo.EXPECT().HandleChainEvent(gomock.Any(), gomock.Any()).Return(testError).AnyTimes().After(precall)
   278  
   279  	wg := sync.WaitGroup{}
   280  	wg.Add(1)
   281  	// trigger stop function after 3 seconds
   282  	go func() {
   283  		defer wg.Done()
   284  		time.Sleep(3 * time.Second)
   285  		fetcher.Stop()
   286  	}()
   287  
   288  	fetcher.repo, fetcher.checkpointDB = mockRepo, checkpointDB
   289  	fetcher.handleRequestByType(cfTypes.RequestTypeAll, true, block1)
   290  	fetcher.handleRequestByType(cfTypes.RequestTypeAll, true, block2)
   291  
   292  	wg.Wait()
   293  	assert.Equal(t, int64(2), fetcher.checkpoint)
   294  }
   295  
   296  func TestChainDataFetcher_setComponents(t *testing.T) {
   297  	ctrl := gomock.NewController(t)
   298  	defer ctrl.Finish()
   299  
   300  	bc, db := mocks.NewMockBlockChain(ctrl), mocks.NewMockCheckpointDB(ctrl)
   301  
   302  	fetcher := &ChainDataFetcher{
   303  		blockchain:   bc,
   304  		checkpointDB: db,
   305  	}
   306  
   307  	// if checkpoint no exist
   308  	testBlockNumber := new(big.Int).SetInt64(5)
   309  	db.EXPECT().ReadCheckpoint().Return(int64(0), nil).Times(1)
   310  	testHeader := &types.Header{Number: testBlockNumber}
   311  	bc.EXPECT().CurrentHeader().Return(testHeader).Times(1)
   312  	fetcher.setCheckpoint()
   313  	assert.Equal(t, testBlockNumber.Int64(), fetcher.checkpoint)
   314  
   315  	// if checkpoint exist
   316  	testCheckpoint := int64(10)
   317  	db.EXPECT().ReadCheckpoint().Return(int64(10), nil).Times(1)
   318  	fetcher.setCheckpoint()
   319  	assert.Equal(t, testCheckpoint, fetcher.checkpoint)
   320  }