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 }