diff --git a/.golangci.yml b/.golangci.yml index 1e89644f0c..e4beaa4478 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -59,6 +59,7 @@ linters: - nolintlint - revive - staticcheck + - tparallel - unconvert - unparam - varcheck diff --git a/dot/core/service_integration_test.go b/dot/core/service_integration_test.go index f60dc989cf..cbe534da09 100644 --- a/dot/core/service_integration_test.go +++ b/dot/core/service_integration_test.go @@ -195,6 +195,7 @@ func TestAnnounceBlock(t *testing.T) { } func TestService_InsertKey(t *testing.T) { + t.Parallel() ks := keystore.NewGlobalKeystore() cfg := &Config{ diff --git a/dot/rpc/modules/author_integration_test.go b/dot/rpc/modules/author_integration_test.go index 385243f763..d337d38d9a 100644 --- a/dot/rpc/modules/author_integration_test.go +++ b/dot/rpc/modules/author_integration_test.go @@ -255,6 +255,7 @@ func TestAuthorModule_SubmitExtrinsic_AlreadyInPool(t *testing.T) { } func TestAuthorModule_InsertKey_Integration(t *testing.T) { + t.Parallel() integrationTestController := setupStateAndRuntime(t, t.TempDir(), useInstanceFromGenesis) auth := newAuthorModule(t, integrationTestController) @@ -336,6 +337,7 @@ func TestAuthorModule_InsertKey_Integration(t *testing.T) { } func TestAuthorModule_HasKey_Integration(t *testing.T) { + t.Parallel() integrationTestController := setupStateAndRuntime(t, t.TempDir(), useInstanceFromGenesis) ks := keystore.NewGlobalKeystore() @@ -403,6 +405,7 @@ func TestAuthorModule_HasKey_Integration(t *testing.T) { } func TestAuthorModule_HasSessionKeys_Integration(t *testing.T) { + t.Parallel() integrationTestController := setupStateAndRuntime(t, t.TempDir(), useInstanceFromGenesis) auth := newAuthorModule(t, integrationTestController) diff --git a/dot/sync/benchmark_test.go b/dot/sync/benchmark_test.go index 4be86456bf..fd8e4f93d0 100644 --- a/dot/sync/benchmark_test.go +++ b/dot/sync/benchmark_test.go @@ -15,6 +15,7 @@ func Test_newSyncBenchmarker(t *testing.T) { t.Parallel() t.Run("10 samples to keep", func(t *testing.T) { + t.Parallel() const samplesToKeep = 10 actual := newSyncBenchmarker(samplesToKeep) @@ -27,6 +28,7 @@ func Test_newSyncBenchmarker(t *testing.T) { }) t.Run("panics on 0 sample to keep", func(t *testing.T) { + t.Parallel() const samplesToKeep = 0 assert.PanicsWithValue(t, "cannot have 0 samples to keep", func() { newSyncBenchmarker(samplesToKeep) diff --git a/dot/sync/bootstrap_syncer_test.go b/dot/sync/bootstrap_syncer_test.go new file mode 100644 index 0000000000..8096d29f9e --- /dev/null +++ b/dot/sync/bootstrap_syncer_test.go @@ -0,0 +1,113 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package sync + +import ( + "errors" + "testing" + + "github.com/ChainSafe/gossamer/dot/types" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" +) + +func Test_bootstrapSyncer_handleWorkerResult(t *testing.T) { + t.Parallel() + mockError := errors.New("mock testing error") + + tests := map[string]struct { + blockStateBuilder func(ctrl *gomock.Controller) BlockState + worker *worker + wantWorkerToRetry *worker + err error + }{ + "nil worker.err returns nil": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + return NewMockBlockState(ctrl) + }, + worker: &worker{}, + }, + "best block header error": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().BestBlockHeader().Return(nil, + mockError) + return mockBlockState + }, + worker: &worker{ + err: &workerError{}, + targetNumber: uintPtr(0), + }, + err: mockError, + }, + "targetNumber < bestBlockHeader number returns nil": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().BestBlockHeader().Return(&types.Header{Number: 2}, nil) + return mockBlockState + }, + worker: &worker{ + err: &workerError{}, + targetNumber: uintPtr(0), + }, + }, + "targetNumber > bestBlockHeader number worker errUnknownParent, error GetHighestFinalisedHeader": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().BestBlockHeader().Return(&types.Header{Number: 2}, nil) + mockBlockState.EXPECT().GetHighestFinalisedHeader().Return(nil, mockError) + return mockBlockState + }, + worker: &worker{ + err: &workerError{err: errUnknownParent}, + targetNumber: uintPtr(3), + }, + err: mockError, + }, + "targetNumber > bestBlockHeader number worker errUnknownParent returns worker": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().BestBlockHeader().Return(&types.Header{Number: 2}, nil) + mockBlockState.EXPECT().GetHighestFinalisedHeader().Return(&types.Header{Number: 1}, nil) + return mockBlockState + }, + worker: &worker{ + err: &workerError{err: errUnknownParent}, + targetNumber: uintPtr(3), + }, + wantWorkerToRetry: &worker{ + startNumber: uintPtr(1), + targetNumber: uintPtr(3), + }, + }, + "targetNumber > bestBlockHeader number returns worker": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().BestBlockHeader().Return(&types.Header{Number: 2}, nil) + return mockBlockState + }, + worker: &worker{ + err: &workerError{}, + targetNumber: uintPtr(3), + }, + wantWorkerToRetry: &worker{ + startNumber: uintPtr(3), + targetNumber: uintPtr(3), + }, + }, + } + for testName, tt := range tests { + tt := tt + t.Run(testName, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + s := &bootstrapSyncer{ + blockState: tt.blockStateBuilder(ctrl), + } + gotWorkerToRetry, err := s.handleWorkerResult(tt.worker) + assert.ErrorIs(t, err, tt.err) + assert.Equal(t, tt.wantWorkerToRetry, gotWorkerToRetry) + }) + } +} diff --git a/dot/sync/chain_processor.go b/dot/sync/chain_processor.go index c3970131ef..de02d5bd33 100644 --- a/dot/sync/chain_processor.go +++ b/dot/sync/chain_processor.go @@ -14,6 +14,8 @@ import ( "github.com/ChainSafe/gossamer/lib/blocktree" ) +//go:generate mockgen -destination=mock_chain_processor_test.go -package=$GOPACKAGE . ChainProcessor + // ChainProcessor processes ready blocks. // it is implemented by *chainProcessor type ChainProcessor interface { @@ -160,7 +162,7 @@ func (s *chainProcessor) processBlockData(bd *types.BlockData) error { logger.Debugf("processing block data with hash %s", bd.Hash) if bd.Header != nil && bd.Body != nil { - if err := s.handleHeader(bd.Header); err != nil { + if err := s.babeVerifier.VerifyBlock(bd.Header); err != nil { return err } @@ -191,16 +193,6 @@ func (s *chainProcessor) processBlockData(bd *types.BlockData) error { return nil } -// handleHeader handles headers included in BlockResponses -func (s *chainProcessor) handleHeader(header *types.Header) error { - err := s.babeVerifier.VerifyBlock(header) - if err != nil { - return fmt.Errorf("%w: %s", ErrInvalidBlock, err.Error()) - } - - return nil -} - // handleHeader handles block bodies included in BlockResponses func (s *chainProcessor) handleBody(body *types.Body) { for _, ext := range *body { @@ -210,10 +202,6 @@ func (s *chainProcessor) handleBody(body *types.Body) { // handleHeader handles blocks (header+body) included in BlockResponses func (s *chainProcessor) handleBlock(block *types.Block) error { - if block == nil || block.Body == nil { - return errors.New("block or body is nil") - } - parent, err := s.blockState.GetHeader(block.Header.ParentHash) if err != nil { return fmt.Errorf("%w: %s", errFailedToGetParent, err) diff --git a/dot/sync/chain_processor_test.go b/dot/sync/chain_processor_test.go new file mode 100644 index 0000000000..0f9cb6624d --- /dev/null +++ b/dot/sync/chain_processor_test.go @@ -0,0 +1,950 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package sync + +import ( + "context" + "errors" + "testing" + + "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/blocktree" + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/runtime/storage" + "github.com/ChainSafe/gossamer/lib/trie" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +//go:generate mockgen -destination=mock_instance_test.go -package=$GOPACKAGE github.com/ChainSafe/gossamer/lib/runtime Instance + +func Test_chainProcessor_handleBlock(t *testing.T) { + t.Parallel() + mockError := errors.New("test mock error") + testHash := common.MustHexToHash("0x03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314") + testParentHash := common.MustHexToHash("0x7db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a") + + tests := map[string]struct { + chainProcessorBuilder func(ctrl *gomock.Controller) chainProcessor + block *types.Block + wantErr error + }{ + "handle getHeader error": { + chainProcessorBuilder: func(ctrl *gomock.Controller) (chainProcessor chainProcessor) { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetHeader(common.Hash{}).Return(nil, mockError) + chainProcessor.blockState = mockBlockState + return + }, + block: &types.Block{ + Body: types.Body{}, + }, + wantErr: errFailedToGetParent, + }, + "handle trieState error": { + chainProcessorBuilder: func(ctrl *gomock.Controller) (chainProcessor chainProcessor) { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetHeader(common.Hash{}).Return(&types.Header{}, nil) + chainProcessor.blockState = mockBlockState + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().Lock() + mockStorageState.EXPECT().TrieState(&common.Hash{}).Return(nil, mockError) + mockStorageState.EXPECT().Unlock() + chainProcessor.storageState = mockStorageState + return + }, + block: &types.Block{ + Body: types.Body{}, + }, + wantErr: mockError, + }, + "handle getRuntime error": { + chainProcessorBuilder: func(ctrl *gomock.Controller) (chainProcessor chainProcessor) { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetHeader(common.Hash{}).Return(&types.Header{ + StateRoot: testHash, + }, nil) + mockBlockState.EXPECT().GetRuntime(&testParentHash).Return(nil, mockError) + chainProcessor.blockState = mockBlockState + trieState := newTrieState(t) + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().Lock() + mockStorageState.EXPECT().TrieState(&testHash).Return(trieState, nil) + mockStorageState.EXPECT().Unlock() + chainProcessor.storageState = mockStorageState + return + }, + block: &types.Block{ + Body: types.Body{}, + }, + wantErr: mockError, + }, + "handle runtime ExecuteBlock error": { + chainProcessorBuilder: func(ctrl *gomock.Controller) (chainProcessor chainProcessor) { + trieState := newTrieState(t) + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetHeader(common.Hash{}).Return(&types.Header{ + StateRoot: testHash, + }, nil) + mockInstance := NewMockInstance(ctrl) + mockInstance.EXPECT().SetContextStorage(trieState) + mockInstance.EXPECT().ExecuteBlock(&types.Block{Body: types.Body{}}).Return(nil, mockError) + mockBlockState.EXPECT().GetRuntime(&testParentHash).Return(mockInstance, nil) + chainProcessor.blockState = mockBlockState + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().Lock() + mockStorageState.EXPECT().TrieState(&testHash).Return(trieState, nil) + mockStorageState.EXPECT().Unlock() + chainProcessor.storageState = mockStorageState + return + }, + block: &types.Block{ + Body: types.Body{}, + }, + wantErr: mockError, + }, + "handle block import error": { + chainProcessorBuilder: func(ctrl *gomock.Controller) (chainProcessor chainProcessor) { + trieState := newTrieState(t) + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetHeader(common.Hash{}).Return(&types.Header{ + StateRoot: testHash, + }, nil) + mockInstance := NewMockInstance(ctrl) + mockInstance.EXPECT().SetContextStorage(trieState) + mockInstance.EXPECT().ExecuteBlock(&types.Block{Body: types.Body{}}).Return(nil, nil) + mockBlockState.EXPECT().GetRuntime(&testParentHash).Return(mockInstance, nil) + chainProcessor.blockState = mockBlockState + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().Lock() + mockStorageState.EXPECT().TrieState(&testHash).Return(trieState, nil) + mockStorageState.EXPECT().Unlock() + chainProcessor.storageState = mockStorageState + mockBlockImportHandler := NewMockBlockImportHandler(ctrl) + mockBlockImportHandler.EXPECT().HandleBlockImport(&types.Block{Body: types.Body{}}, + trieState).Return(mockError) + chainProcessor.blockImportHandler = mockBlockImportHandler + return + }, + block: &types.Block{ + Body: types.Body{}, + }, + wantErr: mockError, + }, + "base case": { + chainProcessorBuilder: func(ctrl *gomock.Controller) (chainProcessor chainProcessor) { + mockBlock := &types.Block{ + Body: types.Body{}, // empty slice of extrinsics + } + trieState := newTrieState(t) + mockBlockState := NewMockBlockState(ctrl) + mockHeader := &types.Header{ + Number: 0, + StateRoot: trie.EmptyHash, + } + mockHeaderHash := mockHeader.Hash() + mockBlockState.EXPECT().GetHeader(common.Hash{}).Return(mockHeader, nil) + mockInstance := NewMockInstance(ctrl) + mockInstance.EXPECT().SetContextStorage(trieState) + mockInstance.EXPECT().ExecuteBlock(mockBlock) + mockBlockState.EXPECT().GetRuntime(&mockHeaderHash).Return(mockInstance, nil) + chainProcessor.blockState = mockBlockState + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().Lock() + mockStorageState.EXPECT().Unlock() + mockStorageState.EXPECT().TrieState(&trie.EmptyHash).Return(trieState, nil) + chainProcessor.storageState = mockStorageState + mockBlockImportHandler := NewMockBlockImportHandler(ctrl) + mockBlockImportHandler.EXPECT().HandleBlockImport(mockBlock, trieState).Return(nil) + chainProcessor.blockImportHandler = mockBlockImportHandler + mockTelemetry := NewMockClient(ctrl) + mockTelemetry.EXPECT().SendMessage(gomock.Any()) + chainProcessor.telemetry = mockTelemetry + return + }, + block: &types.Block{ + Header: types.Header{ + Number: 0, + }, + Body: types.Body{}, + }, + }, + } + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + s := tt.chainProcessorBuilder(ctrl) + + err := s.handleBlock(tt.block) + assert.ErrorIs(t, err, tt.wantErr) + }) + } + t.Run("panics on different parent state root", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + bock := &types.Block{ + Header: types.Header{ + ParentHash: common.Hash{1}, + }, + } + blockState := NewMockBlockState(ctrl) + blockState.EXPECT().GetHeader(common.Hash{1}). + Return(&types.Header{StateRoot: common.Hash{2}}, nil) + trieState := newTrieState(t) + storageState := NewMockStorageState(ctrl) + lockCall := storageState.EXPECT().Lock() + trieStateCall := storageState.EXPECT().TrieState(&common.Hash{2}). + Return(trieState, nil).After(lockCall) + storageState.EXPECT().Unlock().After(trieStateCall) + chainProcessor := &chainProcessor{ + blockState: blockState, + storageState: storageState, + } + const expectedPanicValue = "parent state root does not match snapshot state root" + assert.PanicsWithValue(t, expectedPanicValue, func() { + _ = chainProcessor.handleBlock(bock) + }) + }) +} + +func newTrieState(t *testing.T) *storage.TrieState { + t.Helper() + trieState, err := storage.NewTrieState(nil) + require.NoError(t, err) + return trieState +} + +func Test_chainProcessor_handleBody(t *testing.T) { + t.Parallel() + + testExtrinsics := []types.Extrinsic{{1, 2, 3}, {7, 8, 9, 0}, {0xa, 0xb}} + testBody := types.NewBody(testExtrinsics) + + t.Run("base case", func(t *testing.T) { + ctrl := gomock.NewController(t) + mockTransactionState := NewMockTransactionState(ctrl) + mockTransactionState.EXPECT().RemoveExtrinsic(testExtrinsics[0]) + mockTransactionState.EXPECT().RemoveExtrinsic(testExtrinsics[1]) + mockTransactionState.EXPECT().RemoveExtrinsic(testExtrinsics[2]) + processor := chainProcessor{ + transactionState: mockTransactionState, + } + processor.handleBody(testBody) + }) +} + +func Test_chainProcessor_handleJustification(t *testing.T) { + t.Parallel() + + expectedHash := common.MustHexToHash("0xdcdd89927d8a348e00257e1ecc8617f45edb5118efff3ea2f9961b2ad9b7690a") + + type args struct { + header *types.Header + justification []byte + } + tests := map[string]struct { + chainProcessorBuilder func(ctrl *gomock.Controller) chainProcessor + args args + }{ + "nil justification and header": { + chainProcessorBuilder: func(ctrl *gomock.Controller) chainProcessor { + return chainProcessor{} + }, + }, + "invalid justification": { + chainProcessorBuilder: func(ctrl *gomock.Controller) chainProcessor { + mockFinalityGadget := NewMockFinalityGadget(ctrl) + mockFinalityGadget.EXPECT().VerifyBlockJustification(expectedHash, []byte(`x`)).Return(errors.New("error")) + return chainProcessor{ + finalityGadget: mockFinalityGadget, + } + }, + args: args{ + header: &types.Header{ + Number: 0, + }, + justification: []byte(`x`), + }, + }, + "set justification error": { + chainProcessorBuilder: func(ctrl *gomock.Controller) chainProcessor { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().SetJustification(expectedHash, []byte(`xx`)).Return(errors.New("fake error")) + mockFinalityGadget := NewMockFinalityGadget(ctrl) + mockFinalityGadget.EXPECT().VerifyBlockJustification(expectedHash, []byte(`xx`)).Return(nil) + return chainProcessor{ + blockState: mockBlockState, + finalityGadget: mockFinalityGadget, + } + }, + args: args{ + header: &types.Header{ + Number: 0, + }, + justification: []byte(`xx`), + }, + }, + "base case set": { + chainProcessorBuilder: func(ctrl *gomock.Controller) chainProcessor { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().SetJustification(expectedHash, []byte(`1234`)).Return(nil) + mockFinalityGadget := NewMockFinalityGadget(ctrl) + mockFinalityGadget.EXPECT().VerifyBlockJustification(expectedHash, []byte(`1234`)).Return(nil) + return chainProcessor{ + blockState: mockBlockState, + finalityGadget: mockFinalityGadget, + } + }, + args: args{ + header: &types.Header{ + Number: 0, + }, + justification: []byte(`1234`), + }, + }, + } + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + s := tt.chainProcessorBuilder(ctrl) + s.handleJustification(tt.args.header, tt.args.justification) + }) + } +} + +func Test_chainProcessor_processBlockData(t *testing.T) { + t.Parallel() + + mockError := errors.New("mock test error") + justification := []byte{0, 1, 2} + + tests := map[string]struct { + chainProcessorBuilder func(ctrl *gomock.Controller) chainProcessor + blockData *types.BlockData + expectedError error + }{ + "nil block data": { + chainProcessorBuilder: func(ctrl *gomock.Controller) chainProcessor { + return chainProcessor{} + }, + blockData: nil, + expectedError: ErrNilBlockData, + }, + "handle has header error": { + chainProcessorBuilder: func(ctrl *gomock.Controller) chainProcessor { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HasHeader(common.Hash{}).Return(false, mockError) + return chainProcessor{ + blockState: mockBlockState, + } + }, + blockData: &types.BlockData{}, + expectedError: mockError, + }, + "handle has block body error": { + chainProcessorBuilder: func(ctrl *gomock.Controller) chainProcessor { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HasHeader(common.Hash{}).Return(false, nil) + mockBlockState.EXPECT().HasBlockBody(common.Hash{}).Return(false, mockError) + return chainProcessor{ + blockState: mockBlockState, + } + }, + blockData: &types.BlockData{}, + expectedError: mockError, + }, + "handle getBlockByHash error": { + chainProcessorBuilder: func(ctrl *gomock.Controller) chainProcessor { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HasHeader(common.Hash{}).Return(true, nil) + mockBlockState.EXPECT().HasBlockBody(common.Hash{}).Return(true, nil) + mockBlockState.EXPECT().GetBlockByHash(common.Hash{}).Return(nil, mockError) + return chainProcessor{ + blockState: mockBlockState, + } + }, + blockData: &types.BlockData{}, + expectedError: mockError, + }, + "handle AddBlockToBlockTree error": { + chainProcessorBuilder: func(ctrl *gomock.Controller) chainProcessor { + mockBlock := &types.Block{ + Header: types.Header{ + Number: uint(1), + }, + } + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HasHeader(common.Hash{}).Return(true, nil) + mockBlockState.EXPECT().HasBlockBody(common.Hash{}).Return(true, nil) + mockBlockState.EXPECT().GetBlockByHash(common.Hash{}).Return(mockBlock, nil) + mockBlockState.EXPECT().AddBlockToBlockTree(&types.Block{ + Header: types.Header{Number: 1}}).Return(blocktree.ErrBlockExists) + mockFinalityGadget := NewMockFinalityGadget(ctrl) + mockStorageState := NewMockStorageState(ctrl) + mockBlockImportHandler := NewMockBlockImportHandler(ctrl) + return chainProcessor{ + blockState: mockBlockState, + finalityGadget: mockFinalityGadget, + storageState: mockStorageState, + blockImportHandler: mockBlockImportHandler, + } + }, + blockData: &types.BlockData{ + Justification: &[]byte{1, 2, 3}, + }, + }, + "handle block data justification != nil": { + chainProcessorBuilder: func(ctrl *gomock.Controller) chainProcessor { + mockBlock := &types.Block{ + Header: types.Header{ + Number: uint(1), + }, + } + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HasHeader(common.Hash{}).Return(true, nil) + mockBlockState.EXPECT().HasBlockBody(common.Hash{}).Return(true, nil) + mockBlockState.EXPECT().GetBlockByHash(common.Hash{}).Return(mockBlock, nil) + mockBlockState.EXPECT().AddBlockToBlockTree(&types.Block{ + Header: types.Header{Number: 1}}).Return(nil) + mockBlockState.EXPECT().SetJustification(common.MustHexToHash( + "0x6443a0b46e0412e626363028115a9f2cf963eeed526b8b33e5316f08b50d0dc3"), []byte{1, 2, 3}) + mockFinalityGadget := NewMockFinalityGadget(ctrl) + mockFinalityGadget.EXPECT().VerifyBlockJustification(common.MustHexToHash( + "0x6443a0b46e0412e626363028115a9f2cf963eeed526b8b33e5316f08b50d0dc3"), []byte{1, 2, + 3}) + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().TrieState(&common.Hash{}).Return(nil, nil) + mockBlockImportHandler := NewMockBlockImportHandler(ctrl) + mockBlockImportHandler.EXPECT().HandleBlockImport(mockBlock, + nil).Return(nil) + return chainProcessor{ + blockState: mockBlockState, + finalityGadget: mockFinalityGadget, + storageState: mockStorageState, + blockImportHandler: mockBlockImportHandler, + } + }, + blockData: &types.BlockData{ + Justification: &[]byte{1, 2, 3}, + }, + }, + "handle trie state error": { + chainProcessorBuilder: func(ctrl *gomock.Controller) chainProcessor { + mockBlock := &types.Block{ + Header: types.Header{ + Number: uint(1), + }, + } + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HasHeader(common.Hash{}).Return(true, nil) + mockBlockState.EXPECT().HasBlockBody(common.Hash{}).Return(true, nil) + mockBlockState.EXPECT().GetBlockByHash(common.Hash{}).Return(mockBlock, nil) + mockBlockState.EXPECT().AddBlockToBlockTree(&types.Block{ + Header: types.Header{Number: 1}}).Return(nil) + mockBlockState.EXPECT().SetJustification(common.MustHexToHash( + "0x6443a0b46e0412e626363028115a9f2cf963eeed526b8b33e5316f08b50d0dc3"), []byte{1, 2, 3}) + mockFinalityGadget := NewMockFinalityGadget(ctrl) + mockFinalityGadget.EXPECT().VerifyBlockJustification(common.MustHexToHash( + "0x6443a0b46e0412e626363028115a9f2cf963eeed526b8b33e5316f08b50d0dc3"), []byte{1, 2, + 3}) + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().TrieState(&common.Hash{}).Return(nil, mockError) + return chainProcessor{ + blockState: mockBlockState, + finalityGadget: mockFinalityGadget, + storageState: mockStorageState, + } + }, + blockData: &types.BlockData{ + Justification: &[]byte{1, 2, 3}, + }, + expectedError: mockError, + }, + "handle block import handler error": { + chainProcessorBuilder: func(ctrl *gomock.Controller) chainProcessor { + mockBlock := &types.Block{ + Header: types.Header{ + Number: uint(1), + }, + } + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HasHeader(common.Hash{}).Return(true, nil) + mockBlockState.EXPECT().HasBlockBody(common.Hash{}).Return(true, nil) + mockBlockState.EXPECT().GetBlockByHash(common.Hash{}).Return(mockBlock, nil) + mockBlockState.EXPECT().AddBlockToBlockTree(&types.Block{ + Header: types.Header{Number: 1}}).Return(nil) + mockBlockState.EXPECT().SetJustification(common.MustHexToHash( + "0x6443a0b46e0412e626363028115a9f2cf963eeed526b8b33e5316f08b50d0dc3"), []byte{1, 2, 3}) + mockFinalityGadget := NewMockFinalityGadget(ctrl) + mockFinalityGadget.EXPECT().VerifyBlockJustification(common.MustHexToHash( + "0x6443a0b46e0412e626363028115a9f2cf963eeed526b8b33e5316f08b50d0dc3"), []byte{1, 2, + 3}) + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().TrieState(&common.Hash{}).Return(nil, nil) + mockBlockImportHandler := NewMockBlockImportHandler(ctrl) + mockBlockImportHandler.EXPECT().HandleBlockImport(mockBlock, + nil).Return(mockError) + return chainProcessor{ + blockState: mockBlockState, + finalityGadget: mockFinalityGadget, + storageState: mockStorageState, + blockImportHandler: mockBlockImportHandler, + } + }, + blockData: &types.BlockData{ + Justification: &[]byte{1, 2, 3}, + }, + }, + "has header body false": { + chainProcessorBuilder: func(ctrl *gomock.Controller) chainProcessor { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HasHeader(common.Hash{}).Return(false, nil) + mockBlockState.EXPECT().HasBlockBody(common.Hash{}).Return(false, nil) + mockBlockState.EXPECT().CompareAndSetBlockData(&types.BlockData{}).Return(nil) + return chainProcessor{ + blockState: mockBlockState, + } + }, + blockData: &types.BlockData{}, + }, + "handle babe verify block error": { + chainProcessorBuilder: func(ctrl *gomock.Controller) chainProcessor { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HasHeader(common.Hash{}).Return(false, nil) + mockBlockState.EXPECT().HasBlockBody(common.Hash{}).Return(false, nil) + mockBabeVerifier := NewMockBabeVerifier(ctrl) + mockBabeVerifier.EXPECT().VerifyBlock(&types.Header{}).Return(mockError) + return chainProcessor{ + blockState: mockBlockState, + babeVerifier: mockBabeVerifier, + } + }, + blockData: &types.BlockData{ + Header: &types.Header{}, + Body: &types.Body{}, + }, + expectedError: mockError, + }, + "handle error with handleBlock": { + chainProcessorBuilder: func(ctrl *gomock.Controller) chainProcessor { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HasHeader(common.Hash{}).Return(false, nil) + mockBlockState.EXPECT().HasBlockBody(common.Hash{}).Return(false, nil) + mockBlockState.EXPECT().GetHeader(common.Hash{}).Return(nil, mockError) + mockBabeVerifier := NewMockBabeVerifier(ctrl) + mockBabeVerifier.EXPECT().VerifyBlock(&types.Header{}).Return(nil) + return chainProcessor{ + blockState: mockBlockState, + babeVerifier: mockBabeVerifier, + } + }, + blockData: &types.BlockData{ + Header: &types.Header{}, + Body: &types.Body{}, + }, + expectedError: errFailedToGetParent, + }, + "error adding block": { + chainProcessorBuilder: func(ctrl *gomock.Controller) chainProcessor { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HasHeader(common.Hash{1, 2, 3}).Return(true, nil) + mockBlockState.EXPECT().HasBlockBody(common.Hash{1, 2, 3}).Return(true, nil) + mockBlockState.EXPECT().GetBlockByHash(common.Hash{1, 2, 3}).Return(&types.Block{ + Header: types.Header{ + Number: uint(1), + }, + }, nil) + mockBlockState.EXPECT().AddBlockToBlockTree(&types.Block{ + Header: types.Header{Number: 1}}).Return(mockError) + return chainProcessor{ + blockState: mockBlockState, + } + }, + blockData: &types.BlockData{ + Hash: common.Hash{1, 2, 3}, + }, + expectedError: mockError, + }, + "handle block import": { + chainProcessorBuilder: func(ctrl *gomock.Controller) chainProcessor { + mockTrieState := newTrieState(t) + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HasHeader(common.Hash{1, 2, 3}).Return(true, nil) + mockBlockState.EXPECT().HasBlockBody(common.Hash{1, 2, 3}).Return(true, nil) + mockBlockState.EXPECT().GetBlockByHash(common.Hash{1, 2, 3}).Return(&types.Block{ + Header: types.Header{ + Number: uint(1), + }, + }, nil) + mockBlockState.EXPECT().AddBlockToBlockTree(&types.Block{Header: types.Header{Number: 1}}).Return(nil) + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().TrieState(&common.Hash{}).Return(mockTrieState, nil) + mockBlockImportHandler := NewMockBlockImportHandler(ctrl) + mockBlockImportHandler.EXPECT().HandleBlockImport(&types.Block{Header: types.Header{Number: 1}}, mockTrieState) + return chainProcessor{ + blockState: mockBlockState, + storageState: mockStorageState, + blockImportHandler: mockBlockImportHandler, + } + }, + blockData: &types.BlockData{ + Hash: common.Hash{1, 2, 3}, + }, + }, + "handle compareAndSetBlockData error": { + chainProcessorBuilder: func(ctrl *gomock.Controller) chainProcessor { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HasHeader(common.Hash{}).Return(false, nil) + mockBlockState.EXPECT().HasBlockBody(common.Hash{}).Return(false, nil) + mockBlockState.EXPECT().CompareAndSetBlockData(&types.BlockData{}).Return(mockError) + return chainProcessor{ + blockState: mockBlockState, + } + }, + blockData: &types.BlockData{}, + expectedError: mockError, + }, + "handle header": { + chainProcessorBuilder: func(ctrl *gomock.Controller) chainProcessor { + stateRootHash := common.MustHexToHash("0x03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314") + mockTrieState := newTrieState(t) + runtimeHash := common.MustHexToHash("0x7db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a") + mockInstance := NewMockInstance(ctrl) + mockInstance.EXPECT().SetContextStorage(mockTrieState) + mockInstance.EXPECT().ExecuteBlock(&types.Block{Header: types.Header{}, Body: types.Body{}}) + + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HasHeader(common.Hash{}).Return(false, nil) + mockBlockState.EXPECT().HasBlockBody(common.Hash{}).Return(false, nil) + mockBlockState.EXPECT().GetHeader(common.Hash{}).Return(&types.Header{ + Number: 0, + StateRoot: stateRootHash, + }, nil) + mockBlockState.EXPECT().CompareAndSetBlockData(&types.BlockData{Header: &types.Header{}, Body: &types.Body{}}) + mockBlockState.EXPECT().GetRuntime(&runtimeHash).Return(mockInstance, nil) + mockBabeVerifier := NewMockBabeVerifier(ctrl) + mockBabeVerifier.EXPECT().VerifyBlock(&types.Header{}) + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().Lock() + mockStorageState.EXPECT().TrieState(&stateRootHash).Return(mockTrieState, nil) + mockStorageState.EXPECT().Unlock() + mockBlockImportHandler := NewMockBlockImportHandler(ctrl) + mockBlockImportHandler.EXPECT().HandleBlockImport(&types.Block{ + Header: types.Header{}, Body: types.Body{}}, mockTrieState) + mockTelemetry := NewMockClient(ctrl) + mockTelemetry.EXPECT().SendMessage(gomock.Any()).AnyTimes() + return chainProcessor{ + blockState: mockBlockState, + babeVerifier: mockBabeVerifier, + storageState: mockStorageState, + blockImportHandler: mockBlockImportHandler, + telemetry: mockTelemetry, + } + }, + blockData: &types.BlockData{ + Header: &types.Header{ + Number: 0, + }, + Body: &types.Body{}, + }, + }, + "handle justification": { + chainProcessorBuilder: func(ctrl *gomock.Controller) chainProcessor { + stateRootHash := common.MustHexToHash("0x03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314") + runtimeHash := common.MustHexToHash("0x7db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a") + mockTrieState, _ := storage.NewTrieState(nil) + mockInstance := NewMockInstance(ctrl) + mockInstance.EXPECT().SetContextStorage(mockTrieState) + mockInstance.EXPECT().ExecuteBlock(&types.Block{Header: types.Header{}, Body: types.Body{}}) + + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HasHeader(common.Hash{}).Return(false, nil) + mockBlockState.EXPECT().HasBlockBody(common.Hash{}).Return(false, nil) + mockBlockState.EXPECT().GetHeader(common.Hash{}).Return(&types.Header{ + Number: 0, + StateRoot: stateRootHash, + }, nil) + mockBlockState.EXPECT().SetJustification( + common.MustHexToHash("0xdcdd89927d8a348e00257e1ecc8617f45edb5118efff3ea2f9961b2ad9b7690a"), justification) + mockBlockState.EXPECT().CompareAndSetBlockData(gomock.AssignableToTypeOf(&types.BlockData{})) + mockBlockState.EXPECT().GetRuntime(&runtimeHash).Return(mockInstance, nil) + mockBabeVerifier := NewMockBabeVerifier(ctrl) + mockBabeVerifier.EXPECT().VerifyBlock(&types.Header{}) + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().Lock() + mockStorageState.EXPECT().TrieState(&stateRootHash).Return(mockTrieState, nil) + mockStorageState.EXPECT().Unlock() + mockBlockImportHandler := NewMockBlockImportHandler(ctrl) + mockBlockImportHandler.EXPECT().HandleBlockImport( + &types.Block{Header: types.Header{}, Body: types.Body{}}, mockTrieState) + mockTelemetry := NewMockClient(ctrl) + mockTelemetry.EXPECT().SendMessage(gomock.Any()).AnyTimes() + mockFinalityGadget := NewMockFinalityGadget(ctrl) + mockFinalityGadget.EXPECT().VerifyBlockJustification( + common.MustHexToHash("0xdcdd89927d8a348e00257e1ecc8617f45edb5118efff3ea2f9961b2ad9b7690a"), justification) + return chainProcessor{ + blockState: mockBlockState, + babeVerifier: mockBabeVerifier, + storageState: mockStorageState, + blockImportHandler: mockBlockImportHandler, + telemetry: mockTelemetry, + finalityGadget: mockFinalityGadget, + } + }, + blockData: &types.BlockData{ + Header: &types.Header{ + Number: 0, + }, + Body: &types.Body{}, + Justification: &justification, + }, + }, + } + + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + processor := tt.chainProcessorBuilder(ctrl) + err := processor.processBlockData(tt.blockData) + assert.ErrorIs(t, err, tt.expectedError) + }) + } +} + +func Test_chainProcessor_processReadyBlocks(t *testing.T) { + t.Parallel() + mockError := errors.New("test mock error") + tests := map[string]struct { + blockStateBuilder func(ctrl *gomock.Controller, done chan struct{}) BlockState + blockData *types.BlockData + babeVerifierBuilder func(ctrl *gomock.Controller) BabeVerifier + pendingBlockBuilder func(ctrl *gomock.Controller, done chan struct{}) DisjointBlockSet + storageStateBuilder func(ctrl *gomock.Controller, done chan struct{}) StorageState + }{ + "base case": { + blockStateBuilder: func(ctrl *gomock.Controller, done chan struct{}) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HasHeader(common.Hash{}).Return(false, nil) + mockBlockState.EXPECT().HasBlockBody(common.Hash{}).Return(false, nil) + mockBlockState.EXPECT().CompareAndSetBlockData(&types.BlockData{}).DoAndReturn(func(*types. + BlockData) error { + close(done) + return nil + }) + return mockBlockState + }, + blockData: &types.BlockData{ + Hash: common.Hash{}, + }, + babeVerifierBuilder: func(ctrl *gomock.Controller) BabeVerifier { + return nil + }, + pendingBlockBuilder: func(ctrl *gomock.Controller, done chan struct{}) DisjointBlockSet { + return nil + }, + storageStateBuilder: func(ctrl *gomock.Controller, done chan struct{}) StorageState { + return nil + }, + }, + "add block": { + blockStateBuilder: func(ctrl *gomock.Controller, done chan struct{}) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HasHeader(common.Hash{}).Return(false, nil) + mockBlockState.EXPECT().HasBlockBody(common.Hash{}).Return(false, nil) + mockBlockState.EXPECT().GetHeader(common.Hash{}).Return(nil, mockError) + return mockBlockState + }, + blockData: &types.BlockData{ + Hash: common.Hash{}, + Header: &types.Header{}, + Body: &types.Body{}, + }, + babeVerifierBuilder: func(ctrl *gomock.Controller) BabeVerifier { + mockBabeVerifier := NewMockBabeVerifier(ctrl) + mockBabeVerifier.EXPECT().VerifyBlock(&types.Header{}).Return(nil) + return mockBabeVerifier + }, + pendingBlockBuilder: func(ctrl *gomock.Controller, done chan struct{}) DisjointBlockSet { + mockDisjointBlockSet := NewMockDisjointBlockSet(ctrl) + mockDisjointBlockSet.EXPECT().addBlock(&types.Block{ + Header: types.Header{}, + Body: types.Body{}, + }).DoAndReturn(func(block *types.Block) error { + close(done) + return nil + }) + return mockDisjointBlockSet + }, + storageStateBuilder: func(ctrl *gomock.Controller, done chan struct{}) StorageState { + return nil + }, + }, + "error in process block": { + blockStateBuilder: func(ctrl *gomock.Controller, done chan struct{}) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HasHeader(common.Hash{}).Return(false, nil) + mockBlockState.EXPECT().HasBlockBody(common.Hash{}).Return(false, nil) + mockBlockState.EXPECT().GetHeader(common.Hash{}).Return(&types.Header{}, nil) + return mockBlockState + }, + blockData: &types.BlockData{ + Hash: common.Hash{}, + Header: &types.Header{}, + Body: &types.Body{}, + }, + babeVerifierBuilder: func(ctrl *gomock.Controller) BabeVerifier { + mockBabeVerifier := NewMockBabeVerifier(ctrl) + mockBabeVerifier.EXPECT().VerifyBlock(&types.Header{}).Return(nil) + return mockBabeVerifier + }, + pendingBlockBuilder: func(ctrl *gomock.Controller, done chan struct{}) DisjointBlockSet { + return nil + }, + storageStateBuilder: func(ctrl *gomock.Controller, done chan struct{}) StorageState { + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().Lock() + mockStorageState.EXPECT().Unlock() + mockStorageState.EXPECT().TrieState(&common.Hash{}).DoAndReturn(func(hash *common.Hash) (*storage. + TrieState, error) { + close(done) + return nil, mockError + }) + return mockStorageState + }, + }, + "add block error": { + blockStateBuilder: func(ctrl *gomock.Controller, done chan struct{}) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HasHeader(common.Hash{}).Return(false, nil) + mockBlockState.EXPECT().HasBlockBody(common.Hash{}).Return(false, nil) + mockBlockState.EXPECT().GetHeader(common.Hash{}).Return(nil, mockError) + return mockBlockState + }, + blockData: &types.BlockData{ + Hash: common.Hash{}, + Header: &types.Header{}, + Body: &types.Body{}, + }, + babeVerifierBuilder: func(ctrl *gomock.Controller) BabeVerifier { + mockBabeVerifier := NewMockBabeVerifier(ctrl) + mockBabeVerifier.EXPECT().VerifyBlock(&types.Header{}).Return(nil) + return mockBabeVerifier + }, + pendingBlockBuilder: func(ctrl *gomock.Controller, done chan struct{}) DisjointBlockSet { + mockDisjointBlockSet := NewMockDisjointBlockSet(ctrl) + mockDisjointBlockSet.EXPECT().addBlock(&types.Block{ + Header: types.Header{}, + Body: types.Body{}, + }).DoAndReturn(func(block *types.Block) error { + close(done) + return mockError + }) + return mockDisjointBlockSet + }, + storageStateBuilder: func(ctrl *gomock.Controller, done chan struct{}) StorageState { + return nil + }, + }, + } + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + ctx, cancel := context.WithCancel(context.Background()) + readyBlock := newBlockQueue(5) + done := make(chan struct{}) + + s := &chainProcessor{ + ctx: ctx, + cancel: cancel, + readyBlocks: readyBlock, + blockState: tt.blockStateBuilder(ctrl, done), + babeVerifier: tt.babeVerifierBuilder(ctrl), + pendingBlocks: tt.pendingBlockBuilder(ctrl, done), + storageState: tt.storageStateBuilder(ctrl, done), + } + + go s.processReadyBlocks() + + readyBlock.push(tt.blockData) + <-done + s.cancel() + }) + } +} + +func Test_newChainProcessor(t *testing.T) { + t.Parallel() + + mockReadyBlock := newBlockQueue(5) + mockDisjointBlockSet := NewMockDisjointBlockSet(nil) + mockBlockState := NewMockBlockState(nil) + mockStorageState := NewMockStorageState(nil) + mockTransactionState := NewMockTransactionState(nil) + mockBabeVerifier := NewMockBabeVerifier(nil) + mockFinalityGadget := NewMockFinalityGadget(nil) + mockBlockImportHandler := NewMockBlockImportHandler(nil) + + type args struct { + readyBlocks *blockQueue + pendingBlocks DisjointBlockSet + blockState BlockState + storageState StorageState + transactionState TransactionState + babeVerifier BabeVerifier + finalityGadget FinalityGadget + blockImportHandler BlockImportHandler + } + tests := []struct { + name string + args args + want *chainProcessor + }{ + { + name: "with args", + args: args{ + readyBlocks: mockReadyBlock, + pendingBlocks: mockDisjointBlockSet, + blockState: mockBlockState, + storageState: mockStorageState, + transactionState: mockTransactionState, + babeVerifier: mockBabeVerifier, + finalityGadget: mockFinalityGadget, + blockImportHandler: mockBlockImportHandler, + }, + want: &chainProcessor{ + readyBlocks: mockReadyBlock, + pendingBlocks: mockDisjointBlockSet, + blockState: mockBlockState, + storageState: mockStorageState, + transactionState: mockTransactionState, + babeVerifier: mockBabeVerifier, + finalityGadget: mockFinalityGadget, + blockImportHandler: mockBlockImportHandler, + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := newChainProcessor(tt.args.readyBlocks, tt.args.pendingBlocks, tt.args.blockState, + tt.args.storageState, tt.args.transactionState, tt.args.babeVerifier, tt.args.finalityGadget, + tt.args.blockImportHandler, nil) + assert.NotNil(t, got.ctx) + got.ctx = nil + assert.NotNil(t, got.cancel) + got.cancel = nil + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/dot/sync/chain_sync.go b/dot/sync/chain_sync.go index 7a6e00828d..a31b1f376f 100644 --- a/dot/sync/chain_sync.go +++ b/dot/sync/chain_sync.go @@ -88,7 +88,7 @@ type workHandler interface { handleTick() ([]*worker, error) } -//go:generate mockgen -destination=mock_chain_sync_test.go -package $GOPACKAGE . ChainSync +//go:generate mockgen -destination=mock_chain_sync_test.go -package $GOPACKAGE -source chain_sync.go . ChainSync,workHandler // ChainSync contains the methods used by the high-level service into the `chainSync` module type ChainSync interface { @@ -161,6 +161,8 @@ type chainSync struct { minPeers int maxWorkerRetries uint16 slotDuration time.Duration + + logSyncPeriod time.Duration } type chainSyncConfig struct { @@ -175,6 +177,7 @@ type chainSyncConfig struct { func newChainSync(cfg *chainSyncConfig) *chainSync { ctx, cancel := context.WithCancel(context.Background()) const syncSamplesToKeep = 30 + const logSyncPeriod = 5 * time.Second return &chainSync{ ctx: ctx, cancel: cancel, @@ -194,6 +197,7 @@ func newChainSync(cfg *chainSyncConfig) *chainSync { minPeers: cfg.minPeers, maxWorkerRetries: uint16(cfg.maxPeers), slotDuration: cfg.slotDuration, + logSyncPeriod: logSyncPeriod, } } @@ -267,7 +271,7 @@ func (cs *chainSync) setPeerHead(p peer.ID, hash common.Hash, number uint) error // check if they are on a fork or not head, err := cs.blockState.BestBlockHeader() if err != nil { - return err + return fmt.Errorf("best block header: %w", err) } if ps.number <= head.Number { @@ -275,7 +279,7 @@ func (cs *chainSync) setPeerHead(p peer.ID, hash common.Hash, number uint) error // as we already have that block ourHash, err := cs.blockState.GetHashByNumber(ps.number) if err != nil { - return err + return fmt.Errorf("get block hash by number: %w", err) } if ourHash.Equal(ps.hash) { @@ -287,7 +291,7 @@ func (cs *chainSync) setPeerHead(p peer.ID, hash common.Hash, number uint) error // for now, we can remove them from the syncing peers set fin, err := cs.blockState.GetHighestFinalisedHeader() if err != nil { - return err + return fmt.Errorf("get highest finalised header: %w", err) } // their block hash doesn't match ours for that number (ie. they are on a different @@ -300,14 +304,15 @@ func (cs *chainSync) setPeerHead(p peer.ID, hash common.Hash, number uint) error Value: peerset.BadBlockAnnouncementValue, Reason: peerset.BadBlockAnnouncementReason, }, p) - return errPeerOnInvalidFork + return fmt.Errorf("%w: for peer %s and block number %d", + errPeerOnInvalidFork, p, ps.number) } // peer is on a fork, check if we have processed the fork already or not // ie. is their block written to our db? has, err := cs.blockState.HasHeader(ps.hash) if err != nil { - return err + return fmt.Errorf("has header: %w", err) } // if so, do nothing, as we already have their fork @@ -319,7 +324,7 @@ func (cs *chainSync) setPeerHead(p peer.ID, hash common.Hash, number uint) error // the peer has a higher best block than us, or they are on some fork we are not aware of // add it to the disjoint block set if err = cs.pendingBlocks.addHashAndNumber(ps.hash, ps.number); err != nil { - return err + return fmt.Errorf("add hash and number: %w", err) } cs.workQueue <- ps @@ -328,7 +333,7 @@ func (cs *chainSync) setPeerHead(p peer.ID, hash common.Hash, number uint) error } func (cs *chainSync) logSyncSpeed() { - t := time.NewTicker(time.Second * 5) + t := time.NewTicker(cs.logSyncPeriod) defer t.Stop() for { @@ -411,77 +416,9 @@ func (cs *chainSync) sync() { logger.Errorf("failed to handle chain sync work: %s", err) } case res := <-cs.resultQueue: - // delete worker from workers map - cs.workerState.delete(res.id) - - // handle results from worker - // if there is an error, potentially retry the worker - if res.err == nil || res.ctx.Err() != nil { - continue + if err := cs.handleResult(res); err != nil { + logger.Errorf("failed to handle chain sync result: %s", err) } - - logger.Debugf("worker id %d failed: %s", res.id, res.err.err) - - // handle errors. in the case that a peer did not respond to us in time, - // temporarily add them to the ignore list. - switch { - case errors.Is(res.err.err, context.Canceled): - return - case errors.Is(res.err.err, errNoPeers): - logger.Debugf("worker id %d not able to sync with any peer", res.id) - continue - case errors.Is(res.err.err, context.DeadlineExceeded): - cs.network.ReportPeer(peerset.ReputationChange{ - Value: peerset.TimeOutValue, - Reason: peerset.TimeOutReason, - }, res.err.who) - cs.ignorePeer(res.err.who) - case strings.Contains(res.err.err.Error(), "dial backoff"): - cs.ignorePeer(res.err.who) - continue - case res.err.err.Error() == "protocol not supported": - cs.network.ReportPeer(peerset.ReputationChange{ - Value: peerset.BadProtocolValue, - Reason: peerset.BadProtocolReason, - }, res.err.who) - cs.ignorePeer(res.err.who) - continue - default: - } - - worker, err := cs.handler.handleWorkerResult(res) - if err != nil { - logger.Errorf("failed to handle worker result: %s", err) - continue - } else if worker == nil { - continue - } - - worker.retryCount = res.retryCount + 1 - if worker.retryCount > cs.maxWorkerRetries { - logger.Debugf( - "discarding worker id %d: maximum retry count reached", - worker.id) - - // if this worker was triggered due to a block in the pending blocks set, - // we want to remove it from the set, as we asked all our peers for it - // and none replied with the info we need. - if worker.pendingBlock != nil { - cs.pendingBlocks.removeBlock(worker.pendingBlock.hash) - } - continue - } - - // if we've already tried a peer and there was an error, - // then we shouldn't try them again. - if res.peersTried != nil { - worker.peersTried = res.peersTried - } else { - worker.peersTried = make(map[peer.ID]struct{}) - } - - worker.peersTried[res.err.who] = struct{}{} - cs.tryDispatchWorker(worker) case <-ticker.C: cs.maybeSwitchMode() @@ -525,6 +462,80 @@ func (cs *chainSync) maybeSwitchMode() { } } +func (cs *chainSync) handleResult(resultWorker *worker) error { + // delete worker from workers map + cs.workerState.delete(resultWorker.id) + + // handle results from worker + // if there is an error, potentially retry the worker + if resultWorker.err == nil || resultWorker.ctx.Err() != nil { + return nil + } + + logger.Debugf("worker id %d failed: %s", resultWorker.id, resultWorker.err.err) + + // handle errors. in the case that a peer did not respond to us in time, + // temporarily add them to the ignore list. + switch { + case errors.Is(resultWorker.err.err, context.Canceled): + return nil + case errors.Is(resultWorker.err.err, errNoPeers): + logger.Debugf("worker id %d not able to sync with any peer", resultWorker.id) + return nil + case errors.Is(resultWorker.err.err, context.DeadlineExceeded): + cs.network.ReportPeer(peerset.ReputationChange{ + Value: peerset.TimeOutValue, + Reason: peerset.TimeOutReason, + }, resultWorker.err.who) + cs.ignorePeer(resultWorker.err.who) + case strings.Contains(resultWorker.err.err.Error(), "dial backoff"): + cs.ignorePeer(resultWorker.err.who) + return nil + case resultWorker.err.err.Error() == "protocol not supported": + cs.network.ReportPeer(peerset.ReputationChange{ + Value: peerset.BadProtocolValue, + Reason: peerset.BadProtocolReason, + }, resultWorker.err.who) + cs.ignorePeer(resultWorker.err.who) + return nil + } + + worker, err := cs.handler.handleWorkerResult(resultWorker) + if err != nil { + logger.Errorf("failed to handle worker result: %s", err) + return err + } else if worker == nil { + return nil + } + + worker.retryCount = resultWorker.retryCount + 1 + if worker.retryCount > cs.maxWorkerRetries { + logger.Debugf( + "discarding worker id %d: maximum retry count %d reached", + worker.id, cs.maxWorkerRetries) + + // if this worker was triggered due to a block in the pending blocks set, + // we want to remove it from the set, as we asked all our peers for it + // and none replied with the info we need. + if worker.pendingBlock != nil { + cs.pendingBlocks.removeBlock(worker.pendingBlock.hash) + } + return nil + } + + // if we've already tried a peer and there was an error, + // then we shouldn't try them again. + if resultWorker.peersTried != nil { + worker.peersTried = resultWorker.peersTried + } else { + worker.peersTried = make(map[peer.ID]struct{}) + } + + worker.peersTried[resultWorker.err.who] = struct{}{} + cs.tryDispatchWorker(worker) + return nil +} + // setMode stops all existing workers and clears the worker set and switches the `handler` // based on the new mode, if the mode is different than previous func (cs *chainSync) setMode(mode chainSyncState) { @@ -568,7 +579,7 @@ func (cs *chainSync) getTarget() uint { uintArr = append(uintArr, ps.number) } - sum, count := removeOutliers(uintArr) + sum, count := nonOutliersSumCount(uintArr) quotientBigInt := big.NewInt(0).Div(sum, big.NewInt(int64(count))) return uint(quotientBigInt.Uint64()) } diff --git a/dot/sync/chain_sync_integeration_test.go b/dot/sync/chain_sync_integeration_test.go index d5e12f2499..4c28b52220 100644 --- a/dot/sync/chain_sync_integeration_test.go +++ b/dot/sync/chain_sync_integeration_test.go @@ -1,5 +1,4 @@ //go:build integration -// +build integration // Copyright 2021 ChainSafe Systems (ON) // SPDX-License-Identifier: LGPL-3.0-only @@ -14,151 +13,40 @@ import ( "time" "github.com/ChainSafe/gossamer/dot/network" - syncmocks "github.com/ChainSafe/gossamer/dot/sync/mocks" + "github.com/ChainSafe/gossamer/dot/peerset" + "github.com/ChainSafe/gossamer/dot/sync/mocks" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/common/variadic" "github.com/ChainSafe/gossamer/lib/trie" - + "github.com/golang/mock/gomock" "github.com/libp2p/go-libp2p-core/peer" - "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) -const ( - defaultMinPeers = 1 - defaultMaxPeers = 5 - testTimeout = time.Second * 5 - defaultSlotDuration = time.Second * 6 -) - -func newTestChainSync(t *testing.T) (*chainSync, *blockQueue) { - header, err := types.NewHeader(common.NewHash([]byte{0}), - trie.EmptyHash, trie.EmptyHash, 0, types.NewDigest()) - require.NoError(t, err) - - bs := new(syncmocks.BlockState) - bs.On("BestBlockHeader").Return(header, nil) - bs.On("GetFinalisedNotifierChannel").Return(make(chan *types.FinalisationInfo, 128), nil) - bs.On("HasHeader", mock.AnythingOfType("common.Hash")).Return(true, nil) - - net := new(syncmocks.Network) - net.On("DoBlockRequest", mock.AnythingOfType("peer.ID"), - mock.AnythingOfType("*network.BlockRequestMessage")).Return(nil, nil) - net.On("ReportPeer", mock.AnythingOfType("peerset.ReputationChange"), mock.AnythingOfType("peer.ID")) - - readyBlocks := newBlockQueue(maxResponseSize) - - cfg := &chainSyncConfig{ - bs: bs, - net: net, - readyBlocks: readyBlocks, - pendingBlocks: newDisjointBlockSet(pendingBlocksLimit), - minPeers: defaultMinPeers, - maxPeers: defaultMaxPeers, - slotDuration: defaultSlotDuration, - } - - cs := newChainSync(cfg) - return cs, readyBlocks -} - -func TestChainSync_SetPeerHead(t *testing.T) { - cs, _ := newTestChainSync(t) - - testPeer := peer.ID("noot") - hash := common.Hash{0xa, 0xb} - const number = 1000 - err := cs.setPeerHead(testPeer, hash, number) - require.NoError(t, err) - - expected := &peerState{ - who: testPeer, - hash: hash, - number: number, - } - require.Equal(t, expected, cs.peerState[testPeer]) - require.Equal(t, expected, <-cs.workQueue) - require.True(t, cs.pendingBlocks.hasBlock(hash)) - - // test case where peer has a lower head than us, but they are on the same chain as us - cs.blockState = new(syncmocks.BlockState) - header, err := types.NewHeader(common.NewHash([]byte{0}), - trie.EmptyHash, trie.EmptyHash, number, types.NewDigest()) - require.NoError(t, err) - cs.blockState.(*syncmocks.BlockState).On("BestBlockHeader").Return(header, nil) - fin, err := types.NewHeader(common.NewHash([]byte{0}), - trie.EmptyHash, trie.EmptyHash, number-2, types.NewDigest()) - require.NoError(t, err) - cs.blockState.(*syncmocks.BlockState).On("GetHighestFinalisedHeader").Return(fin, nil) - cs.blockState.(*syncmocks.BlockState).On("GetHashByNumber", mock.AnythingOfType("uint")).Return(hash, nil) - - err = cs.setPeerHead(testPeer, hash, number-1) - require.NoError(t, err) - expected = &peerState{ - who: testPeer, - hash: hash, - number: number - 1, - } - require.Equal(t, expected, cs.peerState[testPeer]) - select { - case <-cs.workQueue: - t.Fatal("should not put chain we already have into work queue") - default: - } - - // test case where peer has a lower head than us, and they are on an invalid fork - cs.blockState = new(syncmocks.BlockState) - cs.blockState.(*syncmocks.BlockState).On("BestBlockHeader").Return(header, nil) - fin, err = types.NewHeader(common.NewHash([]byte{0}), trie.EmptyHash, - trie.EmptyHash, number, types.NewDigest()) +func TestChainSync_sync_bootstrap_withWorkerError_Integration(t *testing.T) { + ctrl := gomock.NewController(t) + cs := newTestChainSync(ctrl) + mockBlockState := NewMockBlockState(ctrl) + mockHeader, err := types.NewHeader(common.NewHash([]byte{0}), trie.EmptyHash, trie.EmptyHash, 0, + types.NewDigest()) require.NoError(t, err) - cs.blockState.(*syncmocks.BlockState).On("GetHighestFinalisedHeader").Return(fin, nil) - cs.blockState.(*syncmocks.BlockState).On("GetHashByNumber", mock.AnythingOfType("uint")).Return(common.Hash{}, nil) - - err = cs.setPeerHead(testPeer, hash, number-1) - require.True(t, errors.Is(err, errPeerOnInvalidFork)) - expected = &peerState{ - who: testPeer, - hash: hash, - number: number - 1, - } - require.Equal(t, expected, cs.peerState[testPeer]) - select { - case <-cs.workQueue: - t.Fatal("should not put invalid fork into work queue") - default: - } - - // test case where peer has a lower head than us, but they are on a valid fork (that is not our chain) - cs.blockState = new(syncmocks.BlockState) - cs.blockState.(*syncmocks.BlockState).On("BestBlockHeader").Return(header, nil) - fin, err = types.NewHeader( - common.NewHash([]byte{0}), trie.EmptyHash, trie.EmptyHash, - number-2, types.NewDigest()) - require.NoError(t, err) - cs.blockState.(*syncmocks.BlockState).On("GetHighestFinalisedHeader").Return(fin, nil) - cs.blockState.(*syncmocks.BlockState).On("GetHashByNumber", mock.AnythingOfType("uint")).Return(common.Hash{}, nil) - cs.blockState.(*syncmocks.BlockState).On("HasHeader", mock.AnythingOfType("common.Hash")).Return(true, nil) - - err = cs.setPeerHead(testPeer, hash, number-1) - require.NoError(t, err) - expected = &peerState{ - who: testPeer, - hash: hash, - number: number - 1, - } - require.Equal(t, expected, cs.peerState[testPeer]) - select { - case <-cs.workQueue: - t.Fatal("should not put fork we already have into work queue") - default: - } -} - -func TestChainSync_sync_bootstrap_withWorkerError(t *testing.T) { - cs, _ := newTestChainSync(t) + mockBlockState.EXPECT().BestBlockHeader().Return(mockHeader, nil).Times(2) + cs.blockState = mockBlockState + cs.handler = newBootstrapSyncer(mockBlockState) + + mockNetwork := NewMockNetwork(ctrl) + startingBlock := variadic.MustNewUint32OrHash(1) + max := uint32(128) + mockNetwork.EXPECT().DoBlockRequest(peer.ID("noot"), &network.BlockRequestMessage{ + RequestedData: 19, + StartingBlock: *startingBlock, + EndBlockHash: nil, + Direction: 0, + Max: &max, + }) + cs.network = mockNetwork go cs.sync() defer cs.cancel() @@ -177,21 +65,22 @@ func TestChainSync_sync_bootstrap_withWorkerError(t *testing.T) { who: testPeer, } require.Equal(t, expected, res.err) - case <-time.After(testTimeout): + case <-time.After(5 * time.Second): t.Fatal("did not get worker response") } require.Equal(t, bootstrap, cs.state) } -func TestChainSync_sync_tip(t *testing.T) { - cs, _ := newTestChainSync(t) - cs.blockState = new(syncmocks.BlockState) - header, err := types.NewHeader(common.NewHash([]byte{0}), - trie.EmptyHash, trie.EmptyHash, 1000, types.NewDigest()) +func TestChainSync_sync_tip_Integration(t *testing.T) { + ctrl := gomock.NewController(t) + cs := newTestChainSync(ctrl) + cs.blockState = new(mocks.BlockState) + header, err := types.NewHeader(common.Hash{0}, trie.EmptyHash, trie.EmptyHash, 1000, + types.NewDigest()) require.NoError(t, err) - cs.blockState.(*syncmocks.BlockState).On("BestBlockHeader").Return(header, nil) - cs.blockState.(*syncmocks.BlockState).On("GetHighestFinalisedHeader").Return(header, nil) + cs.blockState.(*mocks.BlockState).On("BestBlockHeader").Return(header, nil) + cs.blockState.(*mocks.BlockState).On("GetHighestFinalisedHeader").Return(header, nil) go cs.sync() defer cs.cancel() @@ -206,48 +95,7 @@ func TestChainSync_sync_tip(t *testing.T) { require.Equal(t, tip, cs.state) } -func TestChainSync_getTarget(t *testing.T) { - cs, _ := newTestChainSync(t) - - cs.peerState = map[peer.ID]*peerState{ - "a": { - number: 0, // outlier - }, - "b": { - number: 110, - }, - "c": { - number: 120, - }, - "d": { - number: 130, - }, - "e": { - number: 140, - }, - "f": { - number: 150, - }, - "g": { - number: 1000, // outlier - }, - } - - require.Equal(t, uint(130), cs.getTarget()) // sum:650/count:5 = avg:130 - - cs.peerState = map[peer.ID]*peerState{ - "testA": { - number: 1000, - }, - "testB": { - number: 2000, - }, - } - - require.Equal(t, uint(1500), cs.getTarget()) -} - -func TestWorkerToRequests(t *testing.T) { +func TestWorkerToRequests_Integration(t *testing.T) { w := &worker{ startNumber: uintPtr(10), targetNumber: uintPtr(1), @@ -455,7 +303,15 @@ func TestWorkerToRequests(t *testing.T) { } func TestValidateBlockData(t *testing.T) { - cs, _ := newTestChainSync(t) + ctrl := gomock.NewController(t) + cs := newTestChainSync(ctrl) + mockNetwork := NewMockNetwork(ctrl) + mockNetwork.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: -1048576, + Reason: "Incomplete header", + }, peer.ID("")) + cs.network = mockNetwork + req := &network.BlockRequestMessage{ RequestedData: bootstrapRequestData, } @@ -478,8 +334,13 @@ func TestValidateBlockData(t *testing.T) { require.NoError(t, err) } -func TestChainSync_validateResponse(t *testing.T) { - cs, _ := newTestChainSync(t) +func TestChainSync_validateResponse_Integration(t *testing.T) { + ctrl := gomock.NewController(t) + cs := newTestChainSync(ctrl) + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HasHeader(common.Hash{}).Return(true, nil).Times(4) + cs.blockState = mockBlockState + err := cs.validateResponse(nil, nil, "") require.Equal(t, errEmptyBlockData, err) @@ -588,9 +449,10 @@ func TestChainSync_validateResponse(t *testing.T) { require.False(t, cs.pendingBlocks.hasBlock(hash)) } -func TestChainSync_validateResponse_firstBlock(t *testing.T) { - cs, _ := newTestChainSync(t) - bs := new(syncmocks.BlockState) +func TestChainSync_validateResponse_firstBlock_Integration(t *testing.T) { + ctrl := gomock.NewController(t) + cs := newTestChainSync(ctrl) + bs := new(mocks.BlockState) bs.On("HasHeader", mock.AnythingOfType("common.Hash")).Return(false, nil) cs.blockState = bs @@ -624,9 +486,11 @@ func TestChainSync_validateResponse_firstBlock(t *testing.T) { require.NotNil(t, bd.justification) } -func TestChainSync_doSync(t *testing.T) { - cs, readyBlocks := newTestChainSync(t) +func TestChainSync_doSync_Integration(t *testing.T) { + ctrl := gomock.NewController(t) + readyBlocks := newBlockQueue(maxResponseSize) + cs := newTestChainSyncWithReadyBlocks(ctrl, readyBlocks) max := uint32(1) req := &network.BlockRequestMessage{ RequestedData: bootstrapRequestData, @@ -635,6 +499,9 @@ func TestChainSync_doSync(t *testing.T) { Direction: network.Ascending, Max: &max, } + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HasHeader(common.Hash{}).Return(true, nil).Times(2) + cs.blockState = mockBlockState workerErr := cs.doSync(req, make(map[peer.ID]struct{})) require.NotNil(t, workerErr) @@ -644,6 +511,18 @@ func TestChainSync_doSync(t *testing.T) { number: 100, } + mockNetwork := NewMockNetwork(ctrl) + startingBlock := variadic.MustNewUint32OrHash(1) + max1 := uint32(1) + mockNetwork.EXPECT().DoBlockRequest(peer.ID("noot"), &network.BlockRequestMessage{ + RequestedData: 19, + StartingBlock: *startingBlock, + EndBlockHash: nil, + Direction: 0, + Max: &max1, + }) + cs.network = mockNetwork + workerErr = cs.doSync(req, make(map[peer.ID]struct{})) require.NotNil(t, workerErr) require.Equal(t, errNilResponse, workerErr.err) @@ -660,9 +539,8 @@ func TestChainSync_doSync(t *testing.T) { }, } - cs.network = new(syncmocks.Network) - cs.network.(*syncmocks.Network).On("DoBlockRequest", - mock.AnythingOfType("peer.ID"), + cs.network = new(mocks.Network) + cs.network.(*mocks.Network).On("DoBlockRequest", mock.AnythingOfType("peer.ID"), mock.AnythingOfType("*network.BlockRequestMessage")).Return(resp, nil) workerErr = cs.doSync(req, make(map[peer.ID]struct{})) @@ -696,9 +574,8 @@ func TestChainSync_doSync(t *testing.T) { // test to see if descending blocks get reversed req.Direction = network.Descending - cs.network = new(syncmocks.Network) - cs.network.(*syncmocks.Network).On("DoBlockRequest", - mock.AnythingOfType("peer.ID"), + cs.network = new(mocks.Network) + cs.network.(*mocks.Network).On("DoBlockRequest", mock.AnythingOfType("peer.ID"), mock.AnythingOfType("*network.BlockRequestMessage")).Return(resp, nil) workerErr = cs.doSync(req, make(map[peer.ID]struct{})) require.Nil(t, workerErr) @@ -712,8 +589,10 @@ func TestChainSync_doSync(t *testing.T) { require.Equal(t, resp.BlockData[1], bd) } -func TestHandleReadyBlock(t *testing.T) { - cs, readyBlocks := newTestChainSync(t) +func TestHandleReadyBlock_Integration(t *testing.T) { + ctrl := gomock.NewController(t) + readyBlocks := newBlockQueue(maxResponseSize) + cs := newTestChainSyncWithReadyBlocks(ctrl, readyBlocks) // test that descendant chain gets returned by getReadyDescendants on block 1 being ready header1 := &types.Header{ @@ -767,8 +646,9 @@ func TestHandleReadyBlock(t *testing.T) { require.Equal(t, block3.ToBlockData(), readyBlocks.pop(ctx)) } -func TestChainSync_determineSyncPeers(t *testing.T) { - cs, _ := newTestChainSync(t) +func TestChainSync_determineSyncPeers_Integration(t *testing.T) { + ctrl := gomock.NewController(t) + cs := newTestChainSync(ctrl) req := &network.BlockRequestMessage{} testPeerA := peer.ID("a") @@ -817,89 +697,3 @@ func TestChainSync_determineSyncPeers(t *testing.T) { require.Equal(t, 1, len(peers)) require.Equal(t, []peer.ID{testPeerB}, peers) } - -func TestChainSync_highestBlock(t *testing.T) { - type input struct { - peerState map[peer.ID]*peerState - } - type output struct { - highestBlock uint - err error - } - type test struct { - name string - in input - out output - } - tests := []test{ - { - name: "when has an empty map should return 0, errNoPeers", - in: input{ - peerState: map[peer.ID]*peerState{}, - }, - out: output{ - highestBlock: 0, - err: errNoPeers, - }, - }, - { - name: "when has a nil map should return 0, errNoPeers", - in: input{ - peerState: nil, - }, - out: output{ - highestBlock: 0, - err: errNoPeers, - }, - }, - { - name: "when has only one peer with number 90 should return 90, nil", - in: input{ - peerState: map[peer.ID]*peerState{ - "idtest": {number: 90}, - }, - }, - out: output{ - highestBlock: 90, - err: nil, - }, - }, - { - name: "when has two peers (p1, p2) with p1.number 90 and p2.number 190 should return 190, nil", - in: input{ - peerState: map[peer.ID]*peerState{ - "idtest#1": {number: 90}, - "idtest#2": {number: 190}, - }, - }, - out: output{ - highestBlock: 190, - err: nil, - }, - }, - { - name: "when has two peers (p1, p2) with p1.number 190 and p2.number 90 should return 190, nil", - in: input{ - peerState: map[peer.ID]*peerState{ - "idtest#1": {number: 190}, - "idtest#2": {number: 90}, - }, - }, - out: output{ - highestBlock: 190, - err: nil, - }, - }, - } - - for _, ts := range tests { - t.Run(ts.name, func(t *testing.T) { - cs, _ := newTestChainSync(t) - cs.peerState = ts.in.peerState - - highestBlock, err := cs.getHighestBlock() - require.ErrorIs(t, err, ts.out.err) - require.Equal(t, highestBlock, ts.out.highestBlock) - }) - } -} diff --git a/dot/sync/chain_sync_test.go b/dot/sync/chain_sync_test.go new file mode 100644 index 0000000000..ae0e027692 --- /dev/null +++ b/dot/sync/chain_sync_test.go @@ -0,0 +1,1561 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package sync + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/ChainSafe/gossamer/dot/network" + "github.com/ChainSafe/gossamer/dot/peerset" + "github.com/ChainSafe/gossamer/dot/sync/mocks" + "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/common/variadic" + "github.com/ChainSafe/gossamer/lib/trie" + "github.com/golang/mock/gomock" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +const defaultSlotDuration = 6 * time.Second + +func Test_chainSyncState_String(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + s chainSyncState + want string + }{ + { + name: "case bootstrap", + s: bootstrap, + want: "bootstrap", + }, + { + name: "case tip", + s: tip, + want: "tip", + }, + { + name: "case unknown", + s: 3, + want: "unknown", + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := tt.s.String() + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_chainSync_setPeerHead(t *testing.T) { + t.Parallel() + + errTest := errors.New("test error") + const somePeer = peer.ID("abc") + someHash := common.Hash{1, 2, 3, 4} + + testCases := map[string]struct { + chainSyncBuilder func(ctrl *gomock.Controller) *chainSync + peerID peer.ID + hash common.Hash + number uint + errWrapped error + errMessage string + expectedPeerIDToPeerState map[peer.ID]*peerState + expectedQueuedPeerStates []*peerState + }{ + "best block header error": { + chainSyncBuilder: func(ctrl *gomock.Controller) *chainSync { + blockState := NewMockBlockState(ctrl) + blockState.EXPECT().BestBlockHeader().Return(nil, errTest) + return &chainSync{ + peerState: map[peer.ID]*peerState{}, + blockState: blockState, + } + }, + peerID: somePeer, + hash: someHash, + number: 1, + errWrapped: errTest, + errMessage: "best block header: test error", + expectedPeerIDToPeerState: map[peer.ID]*peerState{ + somePeer: { + who: somePeer, + hash: someHash, + number: 1, + }, + }, + }, + "number smaller than best block number get hash by number error": { + chainSyncBuilder: func(ctrl *gomock.Controller) *chainSync { + blockState := NewMockBlockState(ctrl) + bestBlockHeader := &types.Header{Number: 2} + blockState.EXPECT().BestBlockHeader().Return(bestBlockHeader, nil) + blockState.EXPECT().GetHashByNumber(uint(1)). + Return(common.Hash{}, errTest) + return &chainSync{ + peerState: map[peer.ID]*peerState{}, + blockState: blockState, + } + }, + peerID: somePeer, + hash: someHash, + number: 1, + errWrapped: errTest, + errMessage: "get block hash by number: test error", + expectedPeerIDToPeerState: map[peer.ID]*peerState{ + somePeer: { + who: somePeer, + hash: someHash, + number: 1, + }, + }, + }, + "number smaller than best block number and same hash": { + chainSyncBuilder: func(ctrl *gomock.Controller) *chainSync { + blockState := NewMockBlockState(ctrl) + bestBlockHeader := &types.Header{Number: 2} + blockState.EXPECT().BestBlockHeader().Return(bestBlockHeader, nil) + blockState.EXPECT().GetHashByNumber(uint(1)).Return(someHash, nil) + return &chainSync{ + peerState: map[peer.ID]*peerState{}, + blockState: blockState, + } + }, + peerID: somePeer, + hash: someHash, + number: 1, + expectedPeerIDToPeerState: map[peer.ID]*peerState{ + somePeer: { + who: somePeer, + hash: someHash, + number: 1, + }, + }, + }, + "number smaller than best block number get highest finalised header error": { + chainSyncBuilder: func(ctrl *gomock.Controller) *chainSync { + blockState := NewMockBlockState(ctrl) + bestBlockHeader := &types.Header{Number: 2} + blockState.EXPECT().BestBlockHeader().Return(bestBlockHeader, nil) + blockState.EXPECT().GetHashByNumber(uint(1)). + Return(common.Hash{2}, nil) // other hash than someHash + blockState.EXPECT().GetHighestFinalisedHeader().Return(nil, errTest) + return &chainSync{ + peerState: map[peer.ID]*peerState{}, + blockState: blockState, + } + }, + peerID: somePeer, + hash: someHash, + number: 1, + errWrapped: errTest, + errMessage: "get highest finalised header: test error", + expectedPeerIDToPeerState: map[peer.ID]*peerState{ + somePeer: { + who: somePeer, + hash: someHash, + number: 1, + }, + }, + }, + "number smaller than best block number and finalised number equal than number": { + chainSyncBuilder: func(ctrl *gomock.Controller) *chainSync { + blockState := NewMockBlockState(ctrl) + bestBlockHeader := &types.Header{Number: 2} + blockState.EXPECT().BestBlockHeader().Return(bestBlockHeader, nil) + blockState.EXPECT().GetHashByNumber(uint(1)). + Return(common.Hash{2}, nil) // other hash than someHash + finalisedBlockHeader := &types.Header{Number: 1} + blockState.EXPECT().GetHighestFinalisedHeader().Return(finalisedBlockHeader, nil) + network := NewMockNetwork(ctrl) + network.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.BadBlockAnnouncementValue, + Reason: peerset.BadBlockAnnouncementReason, + }, somePeer) + return &chainSync{ + peerState: map[peer.ID]*peerState{}, + blockState: blockState, + network: network, + } + }, + peerID: somePeer, + hash: someHash, + number: 1, + errWrapped: errPeerOnInvalidFork, + errMessage: "peer is on an invalid fork: for peer ZiCa and block number 1", + expectedPeerIDToPeerState: map[peer.ID]*peerState{ + somePeer: { + who: somePeer, + hash: someHash, + number: 1, + }, + }, + }, + "number smaller than best block number and finalised number bigger than number": { + chainSyncBuilder: func(ctrl *gomock.Controller) *chainSync { + blockState := NewMockBlockState(ctrl) + bestBlockHeader := &types.Header{Number: 2} + blockState.EXPECT().BestBlockHeader().Return(bestBlockHeader, nil) + blockState.EXPECT().GetHashByNumber(uint(1)). + Return(common.Hash{2}, nil) // other hash than someHash + finalisedBlockHeader := &types.Header{Number: 2} + blockState.EXPECT().GetHighestFinalisedHeader().Return(finalisedBlockHeader, nil) + network := NewMockNetwork(ctrl) + network.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.BadBlockAnnouncementValue, + Reason: peerset.BadBlockAnnouncementReason, + }, somePeer) + return &chainSync{ + peerState: map[peer.ID]*peerState{}, + blockState: blockState, + network: network, + } + }, + peerID: somePeer, + hash: someHash, + number: 1, + errWrapped: errPeerOnInvalidFork, + errMessage: "peer is on an invalid fork: for peer ZiCa and block number 1", + expectedPeerIDToPeerState: map[peer.ID]*peerState{ + somePeer: { + who: somePeer, + hash: someHash, + number: 1, + }, + }, + }, + "number smaller than best block number and " + + "finalised number smaller than number and " + + "has header error": { + chainSyncBuilder: func(ctrl *gomock.Controller) *chainSync { + blockState := NewMockBlockState(ctrl) + bestBlockHeader := &types.Header{Number: 3} + blockState.EXPECT().BestBlockHeader().Return(bestBlockHeader, nil) + blockState.EXPECT().GetHashByNumber(uint(2)). + Return(common.Hash{2}, nil) // other hash than someHash + finalisedBlockHeader := &types.Header{Number: 1} + blockState.EXPECT().GetHighestFinalisedHeader().Return(finalisedBlockHeader, nil) + blockState.EXPECT().HasHeader(someHash).Return(false, errTest) + return &chainSync{ + peerState: map[peer.ID]*peerState{}, + blockState: blockState, + } + }, + peerID: somePeer, + hash: someHash, + number: 2, + errWrapped: errTest, + errMessage: "has header: test error", + expectedPeerIDToPeerState: map[peer.ID]*peerState{ + somePeer: { + who: somePeer, + hash: someHash, + number: 2, + }, + }, + }, + "number smaller than best block number and " + + "finalised number smaller than number and " + + "has the hash": { + chainSyncBuilder: func(ctrl *gomock.Controller) *chainSync { + blockState := NewMockBlockState(ctrl) + bestBlockHeader := &types.Header{Number: 3} + blockState.EXPECT().BestBlockHeader().Return(bestBlockHeader, nil) + blockState.EXPECT().GetHashByNumber(uint(2)). + Return(common.Hash{2}, nil) // other hash than someHash + finalisedBlockHeader := &types.Header{Number: 1} + blockState.EXPECT().GetHighestFinalisedHeader().Return(finalisedBlockHeader, nil) + blockState.EXPECT().HasHeader(someHash).Return(true, nil) + return &chainSync{ + peerState: map[peer.ID]*peerState{}, + blockState: blockState, + } + }, + peerID: somePeer, + hash: someHash, + number: 2, + expectedPeerIDToPeerState: map[peer.ID]*peerState{ + somePeer: { + who: somePeer, + hash: someHash, + number: 2, + }, + }, + }, + "number bigger than the head number add hash and number error": { + chainSyncBuilder: func(ctrl *gomock.Controller) *chainSync { + blockState := NewMockBlockState(ctrl) + bestBlockHeader := &types.Header{Number: 1} + blockState.EXPECT().BestBlockHeader().Return(bestBlockHeader, nil) + pendingBlocks := NewMockDisjointBlockSet(ctrl) + pendingBlocks.EXPECT().addHashAndNumber(someHash, uint(2)). + Return(errTest) + return &chainSync{ + peerState: map[peer.ID]*peerState{}, + blockState: blockState, + pendingBlocks: pendingBlocks, + } + }, + peerID: somePeer, + hash: someHash, + number: 2, + errWrapped: errTest, + errMessage: "add hash and number: test error", + expectedPeerIDToPeerState: map[peer.ID]*peerState{ + somePeer: { + who: somePeer, + hash: someHash, + number: 2, + }, + }, + }, + "number bigger than the head number success": { + chainSyncBuilder: func(ctrl *gomock.Controller) *chainSync { + blockState := NewMockBlockState(ctrl) + bestBlockHeader := &types.Header{Number: 1} + blockState.EXPECT().BestBlockHeader().Return(bestBlockHeader, nil) + pendingBlocks := NewMockDisjointBlockSet(ctrl) + pendingBlocks.EXPECT().addHashAndNumber(someHash, uint(2)). + Return(nil) + return &chainSync{ + peerState: map[peer.ID]*peerState{}, + blockState: blockState, + pendingBlocks: pendingBlocks, + // buffered of 1 so setPeerHead can write to it + // without a consumer of the channel on the other end. + workQueue: make(chan *peerState, 1), + } + }, + peerID: somePeer, + hash: someHash, + number: 2, + expectedPeerIDToPeerState: map[peer.ID]*peerState{ + somePeer: { + who: somePeer, + hash: someHash, + number: 2, + }, + }, + expectedQueuedPeerStates: []*peerState{ + { + who: somePeer, + hash: someHash, + number: 2, + }, + }, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + + chainSync := testCase.chainSyncBuilder(ctrl) + + err := chainSync.setPeerHead(testCase.peerID, testCase.hash, testCase.number) + + assert.ErrorIs(t, err, testCase.errWrapped) + if testCase.errWrapped != nil { + assert.EqualError(t, err, testCase.errMessage) + } + assert.Equal(t, testCase.expectedPeerIDToPeerState, chainSync.peerState) + + require.Equal(t, len(testCase.expectedQueuedPeerStates), len(chainSync.workQueue)) + for _, expectedPeerState := range testCase.expectedQueuedPeerStates { + peerState := <-chainSync.workQueue + assert.Equal(t, expectedPeerState, peerState) + } + }) + } +} + +func TestChainSync_sync_bootstrap_withWorkerError(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + cs := newTestChainSync(ctrl) + mockBlockState := NewMockBlockState(ctrl) + mockHeader, err := types.NewHeader(common.NewHash([]byte{0}), trie.EmptyHash, trie.EmptyHash, 0, + types.NewDigest()) + require.NoError(t, err) + mockBlockState.EXPECT().BestBlockHeader().Return(mockHeader, nil).Times(2) + cs.blockState = mockBlockState + cs.handler = newBootstrapSyncer(mockBlockState) + + mockNetwork := NewMockNetwork(ctrl) + startingBlock := variadic.MustNewUint32OrHash(1) + max := uint32(128) + mockNetwork.EXPECT().DoBlockRequest(peer.ID("noot"), &network.BlockRequestMessage{ + RequestedData: 19, + StartingBlock: *startingBlock, + EndBlockHash: nil, + Direction: 0, + Max: &max, + }) + cs.network = mockNetwork + + go cs.sync() + defer cs.cancel() + + testPeer := peer.ID("noot") + cs.peerState[testPeer] = &peerState{ + number: 1000, + } + + cs.workQueue <- cs.peerState[testPeer] + + select { + case res := <-cs.resultQueue: + expected := &workerError{ + err: errNilResponse, // since MockNetwork returns a nil response + who: testPeer, + } + require.Equal(t, expected, res.err) + case <-time.After(5 * time.Second): + t.Fatal("did not get worker response") + } + + require.Equal(t, bootstrap, cs.state) +} + +func TestChainSync_sync_tip(t *testing.T) { + t.Parallel() + + done := make(chan struct{}) + + ctrl := gomock.NewController(t) + cs := newTestChainSync(ctrl) + cs.blockState = new(mocks.BlockState) + header, err := types.NewHeader(common.NewHash([]byte{0}), trie.EmptyHash, trie.EmptyHash, 1000, + types.NewDigest()) + require.NoError(t, err) + cs.blockState.(*mocks.BlockState).On("BestBlockHeader").Return(header, nil) + cs.blockState.(*mocks.BlockState).On("GetHighestFinalisedHeader").Run(func(args mock.Arguments) { + close(done) + }).Return(header, nil) + + go cs.sync() + defer cs.cancel() + + testPeer := peer.ID("noot") + cs.peerState[testPeer] = &peerState{ + number: 999, + } + + cs.workQueue <- cs.peerState[testPeer] + <-done + require.Equal(t, tip, cs.state) +} + +func TestChainSync_getTarget(t *testing.T) { + ctrl := gomock.NewController(t) + cs := newTestChainSync(ctrl) + require.Equal(t, uint(1<<32-1), cs.getTarget()) + cs.peerState = map[peer.ID]*peerState{ + "a": { + number: 0, // outlier + }, + "b": { + number: 110, + }, + "c": { + number: 120, + }, + "d": { + number: 130, + }, + "e": { + number: 140, + }, + "f": { + number: 150, + }, + "g": { + number: 1000, // outlier + }, + } + + require.Equal(t, uint(130), cs.getTarget()) // sum:650/count:5= avg:130 + + cs.peerState = map[peer.ID]*peerState{ + "testA": { + number: 1000, + }, + "testB": { + number: 2000, + }, + } + + require.Equal(t, uint(1500), cs.getTarget()) +} + +func TestWorkerToRequests(t *testing.T) { + t.Parallel() + + w := &worker{ + startNumber: uintPtr(10), + targetNumber: uintPtr(1), + direction: network.Ascending, + } + _, err := workerToRequests(w) + require.Equal(t, errInvalidDirection, err) + + type testCase struct { + w *worker + expected []*network.BlockRequestMessage + } + + var ( + max128 = uint32(128) + max9 = uint32(9) + max64 = uint32(64) + ) + + testCases := map[string]testCase{ + "test 0": { + w: &worker{ + startNumber: uintPtr(1), + targetNumber: uintPtr(1 + maxResponseSize), + direction: network.Ascending, + requestData: bootstrapRequestData, + }, + expected: []*network.BlockRequestMessage{ + { + RequestedData: bootstrapRequestData, + StartingBlock: *variadic.MustNewUint32OrHash(1), + EndBlockHash: nil, + Direction: network.Ascending, + Max: &max128, + }, + }, + }, + "test 1": { + w: &worker{ + startNumber: uintPtr(1), + targetNumber: uintPtr(1 + (maxResponseSize * 2)), + direction: network.Ascending, + requestData: bootstrapRequestData, + }, + expected: []*network.BlockRequestMessage{ + { + RequestedData: bootstrapRequestData, + StartingBlock: *variadic.MustNewUint32OrHash(1), + EndBlockHash: nil, + Direction: network.Ascending, + Max: &max128, + }, + { + RequestedData: network.RequestedDataHeader + network.RequestedDataBody + network.RequestedDataJustification, + StartingBlock: *variadic.MustNewUint32OrHash(1 + maxResponseSize), + EndBlockHash: nil, + Direction: network.Ascending, + Max: &max128, + }, + }, + }, + "test 2": { + w: &worker{ + startNumber: uintPtr(1), + targetNumber: uintPtr(10), + direction: network.Ascending, + requestData: bootstrapRequestData, + }, + expected: []*network.BlockRequestMessage{ + { + RequestedData: bootstrapRequestData, + StartingBlock: *variadic.MustNewUint32OrHash(1), + EndBlockHash: nil, + Direction: network.Ascending, + Max: &max128, + }, + }, + }, + "test 3": { + w: &worker{ + startNumber: uintPtr(10), + targetNumber: uintPtr(1), + direction: network.Descending, + requestData: bootstrapRequestData, + }, + expected: []*network.BlockRequestMessage{ + { + RequestedData: bootstrapRequestData, + StartingBlock: *variadic.MustNewUint32OrHash(10), + EndBlockHash: nil, + Direction: network.Descending, + Max: &max9, + }, + }, + }, + "test 4": { + w: &worker{ + startNumber: uintPtr(1), + targetNumber: uintPtr(1 + maxResponseSize + (maxResponseSize / 2)), + direction: network.Ascending, + requestData: bootstrapRequestData, + }, + expected: []*network.BlockRequestMessage{ + { + RequestedData: bootstrapRequestData, + StartingBlock: *variadic.MustNewUint32OrHash(1), + EndBlockHash: nil, + Direction: network.Ascending, + Max: &max128, + }, + { + RequestedData: network.RequestedDataHeader + network.RequestedDataBody + network.RequestedDataJustification, + StartingBlock: *variadic.MustNewUint32OrHash(1 + maxResponseSize), + EndBlockHash: nil, + Direction: network.Ascending, + Max: &max128, + }, + }, + }, + "test 5": { + w: &worker{ + startNumber: uintPtr(1), + targetNumber: uintPtr(10), + targetHash: common.Hash{0xa}, + direction: network.Ascending, + requestData: bootstrapRequestData, + }, + expected: []*network.BlockRequestMessage{ + { + RequestedData: bootstrapRequestData, + StartingBlock: *variadic.MustNewUint32OrHash(1), + EndBlockHash: &(common.Hash{0xa}), + Direction: network.Ascending, + Max: &max128, + }, + }, + }, + "test 6": { + w: &worker{ + startNumber: uintPtr(1), + startHash: common.Hash{0xb}, + targetNumber: uintPtr(10), + targetHash: common.Hash{0xc}, + direction: network.Ascending, + requestData: bootstrapRequestData, + }, + expected: []*network.BlockRequestMessage{ + { + RequestedData: bootstrapRequestData, + StartingBlock: *variadic.MustNewUint32OrHash(common.Hash{0xb}), + EndBlockHash: &(common.Hash{0xc}), + Direction: network.Ascending, + Max: &max128, + }, + }, + }, + "test 7": { + w: &worker{ + startNumber: uintPtr(10), + targetNumber: uintPtr(10), + direction: network.Ascending, + requestData: bootstrapRequestData, + }, + expected: []*network.BlockRequestMessage{ + { + RequestedData: bootstrapRequestData, + StartingBlock: *variadic.MustNewUint32OrHash(10), + Direction: network.Ascending, + Max: &max128, + }, + }, + }, + "test 8": { + w: &worker{ + startNumber: uintPtr(1 + maxResponseSize + (maxResponseSize / 2)), + targetNumber: uintPtr(1), + direction: network.Descending, + requestData: bootstrapRequestData, + }, + expected: []*network.BlockRequestMessage{ + { + RequestedData: network.RequestedDataHeader + network.RequestedDataBody + network.RequestedDataJustification, + StartingBlock: *variadic.MustNewUint32OrHash(1 + (maxResponseSize / 2)), + EndBlockHash: nil, + Direction: network.Descending, + Max: &max64, + }, + { + RequestedData: bootstrapRequestData, + StartingBlock: *variadic.MustNewUint32OrHash(1 + maxResponseSize + (maxResponseSize / 2)), + EndBlockHash: nil, + Direction: network.Descending, + Max: &max128, + }, + }, + }, + } + + for name, tc := range testCases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + reqs, err := workerToRequests(tc.w) + require.NoError(t, err) + require.Equal(t, tc.expected, reqs) + }) + } +} + +func TestChainSync_validateResponse(t *testing.T) { + t.Parallel() + tests := map[string]struct { + blockStateBuilder func(ctrl *gomock.Controller) BlockState + networkBuilder func(ctrl *gomock.Controller) Network + req *network.BlockRequestMessage + resp *network.BlockResponseMessage + expectedError error + }{ + "nil req, nil resp": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetFinalisedNotifierChannel().Return(make(chan *types.FinalisationInfo)) + return mockBlockState + }, + networkBuilder: func(ctrl *gomock.Controller) Network { + return NewMockNetwork(ctrl) + }, + expectedError: errEmptyBlockData, + }, + "handle error response is not chain, has header": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetFinalisedNotifierChannel().Return(make(chan *types.FinalisationInfo)) + mockBlockState.EXPECT().HasHeader(common.Hash{}).Return(true, nil) + return mockBlockState + }, + networkBuilder: func(ctrl *gomock.Controller) Network { + return NewMockNetwork(ctrl) + }, + req: &network.BlockRequestMessage{ + RequestedData: network.RequestedDataHeader, + }, + resp: &network.BlockResponseMessage{ + BlockData: []*types.BlockData{ + { + Header: &types.Header{ + Number: 1, + }, + Body: &types.Body{}, + }, + { + Header: &types.Header{ + Number: 2, + }, + Body: &types.Body{}, + }, + }, + }, + expectedError: errResponseIsNotChain, + }, + "handle justification-only request, unknown block": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetFinalisedNotifierChannel().Return(make(chan *types.FinalisationInfo)) + mockBlockState.EXPECT().HasHeader(common.Hash{}).Return(false, nil) + return mockBlockState + }, + networkBuilder: func(ctrl *gomock.Controller) Network { + mockNetwork := NewMockNetwork(ctrl) + mockNetwork.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.BadJustificationValue, + Reason: peerset.BadJustificationReason, + }, peer.ID("")) + return mockNetwork + }, + req: &network.BlockRequestMessage{ + RequestedData: network.RequestedDataJustification, + }, + resp: &network.BlockResponseMessage{ + BlockData: []*types.BlockData{ + { + Justification: &[]byte{0}, + }, + }, + }, + expectedError: errUnknownBlockForJustification, + }, + "handle error unknown parent": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetFinalisedNotifierChannel().Return(make(chan *types.FinalisationInfo)) + mockBlockState.EXPECT().HasHeader(common.Hash{}).Return(false, nil) + return mockBlockState + }, + networkBuilder: func(ctrl *gomock.Controller) Network { + return NewMockNetwork(ctrl) + }, + req: &network.BlockRequestMessage{ + RequestedData: network.RequestedDataHeader, + }, + resp: &network.BlockResponseMessage{ + BlockData: []*types.BlockData{ + { + Header: &types.Header{ + Number: 1, + }, + Body: &types.Body{}, + }, + { + Header: &types.Header{ + Number: 2, + }, + Body: &types.Body{}, + }, + }, + }, + expectedError: errUnknownParent, + }, + "no error": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetFinalisedNotifierChannel().Return(make(chan *types.FinalisationInfo)) + mockBlockState.EXPECT().HasHeader(common.Hash{}).Return(true, nil) + return mockBlockState + }, + networkBuilder: func(ctrl *gomock.Controller) Network { + return NewMockNetwork(ctrl) + }, + req: &network.BlockRequestMessage{ + RequestedData: network.RequestedDataHeader, + }, + resp: &network.BlockResponseMessage{ + BlockData: []*types.BlockData{ + { + Header: &types.Header{ + Number: 2, + }, + Body: &types.Body{}, + }, + { + Header: &types.Header{ + ParentHash: (&types.Header{ + Number: 2, + }).Hash(), + Number: 3, + }, + Body: &types.Body{}, + }, + }, + }, + }, + } + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + + cfg := &chainSyncConfig{ + bs: tt.blockStateBuilder(ctrl), + pendingBlocks: newDisjointBlockSet(pendingBlocksLimit), + readyBlocks: newBlockQueue(maxResponseSize), + net: tt.networkBuilder(ctrl), + } + cs := newChainSync(cfg) + + err := cs.validateResponse(tt.req, tt.resp, "") + if tt.expectedError != nil { + assert.EqualError(t, err, tt.expectedError.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestChainSync_doSync(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + readyBlocks := newBlockQueue(maxResponseSize) + cs := newTestChainSyncWithReadyBlocks(ctrl, readyBlocks) + + max := uint32(1) + req := &network.BlockRequestMessage{ + RequestedData: bootstrapRequestData, + StartingBlock: *variadic.MustNewUint32OrHash(1), + EndBlockHash: nil, + Direction: network.Ascending, + Max: &max, + } + + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HasHeader(common.Hash{}).Return(true, nil).Times(2) + cs.blockState = mockBlockState + + workerErr := cs.doSync(req, make(map[peer.ID]struct{})) + require.NotNil(t, workerErr) + require.Equal(t, errNoPeers, workerErr.err) + + cs.peerState["noot"] = &peerState{ + number: 100, + } + + mockNetwork := NewMockNetwork(ctrl) + startingBlock := variadic.MustNewUint32OrHash(1) + max1 := uint32(1) + mockNetwork.EXPECT().DoBlockRequest(peer.ID("noot"), &network.BlockRequestMessage{ + RequestedData: 19, + StartingBlock: *startingBlock, + EndBlockHash: nil, + Direction: 0, + Max: &max1, + }) + cs.network = mockNetwork + + workerErr = cs.doSync(req, make(map[peer.ID]struct{})) + require.NotNil(t, workerErr) + require.Equal(t, errNilResponse, workerErr.err) + + resp := &network.BlockResponseMessage{ + BlockData: []*types.BlockData{ + { + Hash: common.Hash{0x1}, + Header: &types.Header{ + Number: 1, + }, + Body: &types.Body{}, + }, + }, + } + + mockNetwork = NewMockNetwork(ctrl) + mockNetwork.EXPECT().DoBlockRequest(peer.ID("noot"), &network.BlockRequestMessage{ + RequestedData: 19, + StartingBlock: *startingBlock, + EndBlockHash: nil, + Direction: 0, + Max: &max1, + }).Return(resp, nil) + cs.network = mockNetwork + + workerErr = cs.doSync(req, make(map[peer.ID]struct{})) + require.Nil(t, workerErr) + bd := readyBlocks.pop(context.Background()) + require.NotNil(t, bd) + require.Equal(t, resp.BlockData[0], bd) + + parent := (&types.Header{ + Number: 2, + }).Hash() + resp = &network.BlockResponseMessage{ + BlockData: []*types.BlockData{ + { + Hash: common.Hash{0x3}, + Header: &types.Header{ + ParentHash: parent, + Number: 3, + }, + Body: &types.Body{}, + }, + { + Hash: common.Hash{0x2}, + Header: &types.Header{ + Number: 2, + }, + Body: &types.Body{}, + }, + }, + } + + // test to see if descending blocks get reversed + req.Direction = network.Descending + mockNetwork = NewMockNetwork(ctrl) + mockNetwork.EXPECT().DoBlockRequest(peer.ID("noot"), &network.BlockRequestMessage{ + RequestedData: 19, + StartingBlock: *startingBlock, + EndBlockHash: nil, + Direction: 1, + Max: &max1, + }).Return(resp, nil) + cs.network = mockNetwork + workerErr = cs.doSync(req, make(map[peer.ID]struct{})) + require.Nil(t, workerErr) + + bd = readyBlocks.pop(context.Background()) + require.NotNil(t, bd) + require.Equal(t, resp.BlockData[0], bd) + + bd = readyBlocks.pop(context.Background()) + require.NotNil(t, bd) + require.Equal(t, resp.BlockData[1], bd) +} + +func TestHandleReadyBlock(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + readyBlocks := newBlockQueue(maxResponseSize) + cs := newTestChainSyncWithReadyBlocks(ctrl, readyBlocks) + + // test that descendant chain gets returned by getReadyDescendants on block 1 being ready + header1 := &types.Header{ + Number: 1, + } + block1 := &types.Block{ + Header: *header1, + Body: types.Body{}, + } + + header2 := &types.Header{ + ParentHash: header1.Hash(), + Number: 2, + } + block2 := &types.Block{ + Header: *header2, + Body: types.Body{}, + } + cs.pendingBlocks.addBlock(block2) + + header3 := &types.Header{ + ParentHash: header2.Hash(), + Number: 3, + } + block3 := &types.Block{ + Header: *header3, + Body: types.Body{}, + } + cs.pendingBlocks.addBlock(block3) + + header2NotDescendant := &types.Header{ + ParentHash: common.Hash{0xff}, + Number: 2, + } + block2NotDescendant := &types.Block{ + Header: *header2NotDescendant, + Body: types.Body{}, + } + cs.pendingBlocks.addBlock(block2NotDescendant) + + cs.handleReadyBlock(block1.ToBlockData()) + + require.False(t, cs.pendingBlocks.hasBlock(header1.Hash())) + require.False(t, cs.pendingBlocks.hasBlock(header2.Hash())) + require.False(t, cs.pendingBlocks.hasBlock(header3.Hash())) + require.True(t, cs.pendingBlocks.hasBlock(header2NotDescendant.Hash())) + + require.Equal(t, block1.ToBlockData(), readyBlocks.pop(context.Background())) + require.Equal(t, block2.ToBlockData(), readyBlocks.pop(context.Background())) + require.Equal(t, block3.ToBlockData(), readyBlocks.pop(context.Background())) +} + +func TestChainSync_determineSyncPeers(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + cs := newTestChainSync(ctrl) + + req := &network.BlockRequestMessage{} + testPeerA := peer.ID("a") + testPeerB := peer.ID("b") + peersTried := make(map[peer.ID]struct{}) + + // test base case + cs.peerState[testPeerA] = &peerState{ + number: 129, + } + cs.peerState[testPeerB] = &peerState{ + number: 257, + } + + peers := cs.determineSyncPeers(req, peersTried) + require.Equal(t, 2, len(peers)) + require.Contains(t, peers, testPeerA) + require.Contains(t, peers, testPeerB) + + // test peer ignored case + cs.ignorePeers[testPeerA] = struct{}{} + peers = cs.determineSyncPeers(req, peersTried) + require.Equal(t, 1, len(peers)) + require.Equal(t, []peer.ID{testPeerB}, peers) + + // test all peers ignored case + cs.ignorePeers[testPeerB] = struct{}{} + peers = cs.determineSyncPeers(req, peersTried) + require.Equal(t, 2, len(peers)) + require.Contains(t, peers, testPeerA) + require.Contains(t, peers, testPeerB) + require.Equal(t, 0, len(cs.ignorePeers)) + + // test peer's best block below number case, shouldn't include that peer + start, err := variadic.NewUint32OrHash(130) + require.NoError(t, err) + req.StartingBlock = *start + peers = cs.determineSyncPeers(req, peersTried) + require.Equal(t, 1, len(peers)) + require.Equal(t, []peer.ID{testPeerB}, peers) + + // test peer tried case, should ignore peer already tried + peersTried[testPeerA] = struct{}{} + req.StartingBlock = variadic.Uint32OrHash{} + peers = cs.determineSyncPeers(req, peersTried) + require.Equal(t, 1, len(peers)) + require.Equal(t, []peer.ID{testPeerB}, peers) +} + +func Test_chainSync_logSyncSpeed(t *testing.T) { + t.Parallel() + + type fields struct { + blockStateBuilder func(ctrl *gomock.Controller) BlockState + networkBuilder func(ctrl *gomock.Controller, done chan struct{}) Network + state chainSyncState + benchmarker *syncBenchmarker + } + tests := []struct { + name string + fields fields + }{ + { + name: "state bootstrap", + fields: fields{ + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().BestBlockHeader().Return(&types.Header{}, nil).AnyTimes() + mockBlockState.EXPECT().GetHighestFinalisedHeader().Return(&types.Header{}, nil) + return mockBlockState + }, + networkBuilder: func(ctrl *gomock.Controller, done chan struct{}) Network { + mockNetwork := NewMockNetwork(ctrl) + mockNetwork.EXPECT().Peers().DoAndReturn(func() error { + close(done) + return nil + }) + return mockNetwork + }, + benchmarker: newSyncBenchmarker(10), + state: bootstrap, + }, + }, + { + name: "case tip", + fields: fields{ + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().BestBlockHeader().Return(&types.Header{}, nil).AnyTimes() + mockBlockState.EXPECT().GetHighestFinalisedHeader().Return(&types.Header{}, nil) + return mockBlockState + }, + networkBuilder: func(ctrl *gomock.Controller, done chan struct{}) Network { + mockNetwork := NewMockNetwork(ctrl) + mockNetwork.EXPECT().Peers().DoAndReturn(func() error { + close(done) + return nil + }) + return mockNetwork + }, + benchmarker: newSyncBenchmarker(10), + state: tip, + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + ctx, cancel := context.WithCancel(context.Background()) + done := make(chan struct{}) + cs := &chainSync{ + ctx: ctx, + cancel: cancel, + blockState: tt.fields.blockStateBuilder(ctrl), + network: tt.fields.networkBuilder(ctrl, done), + state: tt.fields.state, + benchmarker: tt.fields.benchmarker, + logSyncPeriod: time.Millisecond, + } + go cs.logSyncSpeed() + <-done + cancel() + }) + } +} + +func Test_chainSync_start(t *testing.T) { + t.Parallel() + + type fields struct { + blockStateBuilder func(ctrl *gomock.Controller) BlockState + disjointBlockSetBuilder func(ctrl *gomock.Controller) DisjointBlockSet + networkBuilder func(ctrl *gomock.Controller, done chan struct{}) Network + benchmarker *syncBenchmarker + slotDuration time.Duration + } + tests := []struct { + name string + fields fields + }{ + { + name: "base case", + fields: fields{ + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().BestBlockHeader().Return(&types.Header{}, nil).AnyTimes() + mockBlockState.EXPECT().GetHighestFinalisedHeader().Return(&types.Header{}, nil) + mockBlockState.EXPECT().BestBlockHeader().Return(&types.Header{}, nil).AnyTimes() + return mockBlockState + }, + disjointBlockSetBuilder: func(ctrl *gomock.Controller) DisjointBlockSet { + mockDisjointBlockSet := NewMockDisjointBlockSet(ctrl) + mockDisjointBlockSet.EXPECT().run(gomock.Any()) + return mockDisjointBlockSet + }, + networkBuilder: func(ctrl *gomock.Controller, done chan struct{}) Network { + mockNetwork := NewMockNetwork(ctrl) + mockNetwork.EXPECT().Peers().DoAndReturn(func() []common.PeerInfo { + close(done) + return nil + }) + return mockNetwork + }, + slotDuration: defaultSlotDuration, + benchmarker: newSyncBenchmarker(1), + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + ctx, cancel := context.WithCancel(context.Background()) + done := make(chan struct{}) + cs := &chainSync{ + ctx: ctx, + cancel: cancel, + blockState: tt.fields.blockStateBuilder(ctrl), + pendingBlocks: tt.fields.disjointBlockSetBuilder(ctrl), + network: tt.fields.networkBuilder(ctrl, done), + benchmarker: tt.fields.benchmarker, + slotDuration: tt.fields.slotDuration, + logSyncPeriod: time.Second, + } + cs.start() + <-done + cs.stop() + }) + } +} + +func Test_chainSync_setBlockAnnounce(t *testing.T) { + type args struct { + from peer.ID + header *types.Header + } + tests := map[string]struct { + chainSyncBuilder func(ctrl *gomock.Controller) chainSync + args args + wantErr error + }{ + "base case": { + args: args{ + header: &types.Header{Number: 2}, + }, + chainSyncBuilder: func(ctrl *gomock.Controller) chainSync { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HasHeader(common.MustHexToHash( + "0x05bdcc454f60a08d427d05e7f19f240fdc391f570ab76fcb96ecca0b5823d3bf")).Return(true, nil) + mockDisjointBlockSet := NewMockDisjointBlockSet(ctrl) + return chainSync{ + blockState: mockBlockState, + pendingBlocks: mockDisjointBlockSet, + } + }, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + ctrl := gomock.NewController(t) + sync := tt.chainSyncBuilder(ctrl) + err := sync.setBlockAnnounce(tt.args.from, tt.args.header) + assert.ErrorIs(t, err, tt.wantErr) + }) + } +} + +func Test_chainSync_getHighestBlock(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + peerState map[peer.ID]*peerState + wantHighestBlock uint + expectedError error + }{ + { + name: "error no peers", + expectedError: errors.New("no peers to sync with"), + }, + { + name: "base case", + peerState: map[peer.ID]*peerState{"1": {number: 2}}, + wantHighestBlock: 2, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + cs := &chainSync{ + peerState: tt.peerState, + } + gotHighestBlock, err := cs.getHighestBlock() + if tt.expectedError != nil { + assert.EqualError(t, err, tt.expectedError.Error()) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tt.wantHighestBlock, gotHighestBlock) + }) + } +} + +func Test_chainSync_handleResult(t *testing.T) { + t.Parallel() + mockError := errors.New("test mock error") + tests := map[string]struct { + chainSyncBuilder func(ctrl *gomock.Controller, result *worker) chainSync + maxWorkerRetries uint16 + res *worker + err error + }{ + "res.err == nil": { + chainSyncBuilder: func(ctrl *gomock.Controller, result *worker) chainSync { + return chainSync{ + workerState: newWorkerState(), + } + }, + res: &worker{}, + }, + "res.err.err.Error() == context.Canceled": { + chainSyncBuilder: func(ctrl *gomock.Controller, result *worker) chainSync { + return chainSync{ + workerState: newWorkerState(), + } + }, + res: &worker{ + ctx: context.Background(), + err: &workerError{ + err: context.Canceled, + }, + }, + }, + "res.err.err.Error() == context.DeadlineExceeded": { + chainSyncBuilder: func(ctrl *gomock.Controller, result *worker) chainSync { + mockNetwork := NewMockNetwork(ctrl) + mockNetwork.EXPECT().ReportPeer(peerset.ReputationChange{Value: -1024, Reason: "Request timeout"}, + peer.ID("")) + mockWorkHandler := NewMockworkHandler(ctrl) + mockWorkHandler.EXPECT().handleWorkerResult(result).Return(result, nil) + return chainSync{ + workerState: newWorkerState(), + network: mockNetwork, + handler: mockWorkHandler, + } + }, + res: &worker{ + ctx: context.Background(), + err: &workerError{ + err: context.DeadlineExceeded, + }, + }, + }, + "res.err.err.Error() dial backoff": { + chainSyncBuilder: func(ctrl *gomock.Controller, result *worker) chainSync { + return chainSync{ + workerState: newWorkerState(), + } + }, + res: &worker{ + ctx: context.Background(), + err: &workerError{ + err: errors.New("dial backoff"), + }, + }, + }, + "res.err.err.Error() == errNoPeers": { + chainSyncBuilder: func(ctrl *gomock.Controller, result *worker) chainSync { + return chainSync{ + workerState: newWorkerState(), + } + }, + res: &worker{ + ctx: context.Background(), + err: &workerError{ + err: errNoPeers, + }, + }, + }, + "res.err.err.Error() == protocol not supported": { + chainSyncBuilder: func(ctrl *gomock.Controller, result *worker) chainSync { + mockNetwork := NewMockNetwork(ctrl) + mockNetwork.EXPECT().ReportPeer(peerset.ReputationChange{Value: -2147483648, + Reason: "Unsupported protocol"}, + peer.ID("")) + return chainSync{ + workerState: newWorkerState(), + network: mockNetwork, + } + }, + res: &worker{ + ctx: context.Background(), + err: &workerError{ + err: errors.New("protocol not supported"), + }, + }, + }, + "no error, no retries": { + chainSyncBuilder: func(ctrl *gomock.Controller, result *worker) chainSync { + mockWorkHandler := NewMockworkHandler(ctrl) + mockWorkHandler.EXPECT().handleWorkerResult(result).Return(result, nil) + return chainSync{ + workerState: newWorkerState(), + handler: mockWorkHandler, + } + }, + res: &worker{ + ctx: context.Background(), + err: &workerError{ + err: errors.New(""), + }, + }, + }, + "handle work result error, no retries": { + chainSyncBuilder: func(ctrl *gomock.Controller, result *worker) chainSync { + mockWorkHandler := NewMockworkHandler(ctrl) + mockWorkHandler.EXPECT().handleWorkerResult(result).Return(nil, mockError) + return chainSync{ + workerState: newWorkerState(), + handler: mockWorkHandler, + } + }, + res: &worker{ + ctx: context.Background(), + err: &workerError{ + err: errors.New(""), + }, + }, + err: mockError, + }, + "handle work result nil, no retries": { + chainSyncBuilder: func(ctrl *gomock.Controller, result *worker) chainSync { + mockWorkHandler := NewMockworkHandler(ctrl) + mockWorkHandler.EXPECT().handleWorkerResult(result).Return(nil, nil) + return chainSync{ + workerState: newWorkerState(), + handler: mockWorkHandler, + } + }, + res: &worker{ + ctx: context.Background(), + err: &workerError{ + err: errors.New(""), + }, + }, + }, + "no error, maxWorkerRetries 2": { + chainSyncBuilder: func(ctrl *gomock.Controller, result *worker) chainSync { + mockWorkHandler := NewMockworkHandler(ctrl) + mockWorkHandler.EXPECT().handleWorkerResult(result).Return(result, nil) + mockDisjointBlockSet := NewMockDisjointBlockSet(ctrl) + mockDisjointBlockSet.EXPECT().removeBlock(common.Hash{}) + return chainSync{ + workerState: newWorkerState(), + handler: mockWorkHandler, + pendingBlocks: mockDisjointBlockSet, + } + }, + maxWorkerRetries: 2, + res: &worker{ + ctx: context.Background(), + err: &workerError{ + err: errors.New(""), + }, + pendingBlock: newPendingBlock(common.Hash{}, 1, nil, nil, time.Now()), + }, + }, + "no error": { + chainSyncBuilder: func(ctrl *gomock.Controller, result *worker) chainSync { + mockWorkHandler := NewMockworkHandler(ctrl) + mockWorkHandler.EXPECT().handleWorkerResult(result).Return(result, nil) + mockWorkHandler.EXPECT().hasCurrentWorker(&worker{ + ctx: context.Background(), + err: &workerError{ + err: mockError, + }, + retryCount: 1, + peersTried: map[peer.ID]struct{}{ + "": {}, + }, + }, newWorkerState().workers).Return(true) + return chainSync{ + workerState: newWorkerState(), + handler: mockWorkHandler, + maxWorkerRetries: 2, + } + }, + res: &worker{ + ctx: context.Background(), + err: &workerError{ + err: mockError, + }, + }, + }, + } + for testName, tt := range tests { + tt := tt + t.Run(testName, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + sync := tt.chainSyncBuilder(ctrl, tt.res) + err := sync.handleResult(tt.res) + if tt.err != nil { + assert.EqualError(t, err, tt.err.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} + +func newTestChainSyncWithReadyBlocks(ctrl *gomock.Controller, readyBlocks *blockQueue) *chainSync { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetFinalisedNotifierChannel().Return(make(chan *types.FinalisationInfo)) + + cfg := &chainSyncConfig{ + bs: mockBlockState, + readyBlocks: readyBlocks, + pendingBlocks: newDisjointBlockSet(pendingBlocksLimit), + minPeers: 1, + maxPeers: 5, + slotDuration: defaultSlotDuration, + } + + return newChainSync(cfg) +} + +func newTestChainSync(ctrl *gomock.Controller) *chainSync { + readyBlocks := newBlockQueue(maxResponseSize) + return newTestChainSyncWithReadyBlocks(ctrl, readyBlocks) +} diff --git a/dot/sync/disjoint_block_set.go b/dot/sync/disjoint_block_set.go index 71f65a78ec..78dbde3abe 100644 --- a/dot/sync/disjoint_block_set.go +++ b/dot/sync/disjoint_block_set.go @@ -23,6 +23,8 @@ var ( errSetAtLimit = errors.New("cannot add block; set is at capacity") ) +//go:generate mockgen -destination=mock_disjoint_block_set_test.go -package=$GOPACKAGE . DisjointBlockSet + // DisjointBlockSet represents a set of incomplete blocks, or blocks // with an unknown parent. it is implemented by *disjointBlockSet type DisjointBlockSet interface { diff --git a/dot/sync/disjoint_block_set_integeration_test.go b/dot/sync/disjoint_block_set_integeration_test.go index afcd7458b3..28d3924875 100644 --- a/dot/sync/disjoint_block_set_integeration_test.go +++ b/dot/sync/disjoint_block_set_integeration_test.go @@ -12,15 +12,12 @@ import ( "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" - + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestDisjointBlockSet(t *testing.T) { s := newDisjointBlockSet(pendingBlocksLimit) - s.timeNow = func() time.Time { - return time.Time{} - } hash := common.Hash{0xa, 0xb} const number uint = 100 @@ -29,12 +26,13 @@ func TestDisjointBlockSet(t *testing.T) { require.Equal(t, 1, s.size()) expected := &pendingBlock{ - hash: hash, - number: number, - clearAt: time.Time{}.Add(ttl), + hash: hash, + number: number, } blocks := s.getBlocks() require.Equal(t, 1, len(blocks)) + assert.Greater(t, blocks[0].clearAt, time.Now().Add(ttl-time.Minute)) + blocks[0].clearAt = time.Time{} require.Equal(t, expected, blocks[0]) header := &types.Header{ @@ -43,14 +41,15 @@ func TestDisjointBlockSet(t *testing.T) { s.addHeader(header) require.True(t, s.hasBlock(header.Hash())) require.Equal(t, 2, s.size()) - expected = &pendingBlock{ - hash: header.Hash(), - number: header.Number, - header: header, - clearAt: time.Time{}.Add(ttl), + hash: header.Hash(), + number: header.Number, + header: header, } - require.Equal(t, expected, s.getBlock(header.Hash())) + block1 := s.getBlock(header.Hash()) + assert.Greater(t, block1.clearAt, time.Now().Add(ttl-time.Minute)) + block1.clearAt = time.Time{} + require.Equal(t, expected, block1) header2 := &types.Header{ Number: 999, @@ -60,12 +59,14 @@ func TestDisjointBlockSet(t *testing.T) { s.addHeader(header2) require.Equal(t, 3, s.size()) expected = &pendingBlock{ - hash: header2.Hash(), - number: header2.Number, - header: header2, - clearAt: time.Time{}.Add(ttl), + hash: header2.Hash(), + number: header2.Number, + header: header2, } - require.Equal(t, expected, s.getBlock(header2.Hash())) + block2 := s.getBlock(header2.Hash()) + assert.Greater(t, block2.clearAt, time.Now().Add(ttl-time.Minute)) + block2.clearAt = time.Time{} + require.Equal(t, expected, block2) block := &types.Block{ Header: *header2, @@ -74,13 +75,15 @@ func TestDisjointBlockSet(t *testing.T) { s.addBlock(block) require.Equal(t, 3, s.size()) expected = &pendingBlock{ - hash: header2.Hash(), - number: header2.Number, - header: header2, - body: &block.Body, - clearAt: time.Time{}.Add(ttl), - } - require.Equal(t, expected, s.getBlock(header2.Hash())) + hash: header2.Hash(), + number: header2.Number, + header: header2, + body: &block.Body, + } + block3 := s.getBlock(header2.Hash()) + assert.Greater(t, block3.clearAt, time.Now().Add(ttl-time.Minute)) + block3.clearAt = time.Time{} + require.Equal(t, expected, block3) s.removeBlock(hash) require.Equal(t, 2, s.size()) diff --git a/dot/sync/disjoint_block_set_test.go b/dot/sync/disjoint_block_set_test.go new file mode 100644 index 0000000000..d954e4b0a7 --- /dev/null +++ b/dot/sync/disjoint_block_set_test.go @@ -0,0 +1,484 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package sync + +import ( + "errors" + "testing" + "time" + + "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/common" + "github.com/stretchr/testify/assert" +) + +func Test_disjointBlockSet_addBlock(t *testing.T) { + t.Parallel() + + hashHeader := func(header types.Header) common.Hash { + return header.Hash() + } + setHashToHeader := func(header types.Header) *types.Header { + header.Hash() + return &header + } + + timeNow := func() time.Time { + return time.Unix(0, 0) + } + tests := map[string]struct { + disjointBlockSet *disjointBlockSet + block *types.Block + expectedDisjointBlockSet *disjointBlockSet + err error + }{ + "add block beyond capacity": { + disjointBlockSet: &disjointBlockSet{}, + block: &types.Block{ + Header: types.Header{ + Number: 1, + }, + }, + expectedDisjointBlockSet: &disjointBlockSet{}, + err: errSetAtLimit, + }, + "add block": { + disjointBlockSet: &disjointBlockSet{ + limit: 1, + blocks: make(map[common.Hash]*pendingBlock), + timeNow: timeNow, + parentToChildren: make(map[common.Hash]map[common.Hash]struct{}), + }, + block: &types.Block{ + Header: types.Header{ + Number: 1, + ParentHash: common.Hash{1}, + }, + Body: []types.Extrinsic{[]byte{1}}, + }, + expectedDisjointBlockSet: &disjointBlockSet{ + limit: 1, + blocks: map[common.Hash]*pendingBlock{ + hashHeader(types.Header{Number: 1, ParentHash: common.Hash{1}}): { + hash: hashHeader(types.Header{Number: 1, ParentHash: common.Hash{1}}), + number: 1, + header: setHashToHeader(types.Header{Number: 1, ParentHash: common.Hash{1}}), + body: &types.Body{{1}}, + clearAt: time.Unix(0, int64(ttl)), + }, + }, + parentToChildren: map[common.Hash]map[common.Hash]struct{}{ + {1}: { + hashHeader(types.Header{Number: 1, ParentHash: common.Hash{1}}): {}, + }, + }, + }, + }, + "has block": { + disjointBlockSet: &disjointBlockSet{ + limit: 1, + blocks: map[common.Hash]*pendingBlock{ + hashHeader(types.Header{Number: 1, ParentHash: common.Hash{1}}): { + hash: hashHeader(types.Header{Number: 1, ParentHash: common.Hash{1}}), + number: 1, + header: setHashToHeader(types.Header{Number: 1, ParentHash: common.Hash{1}}), + body: &types.Body{{1}}, + clearAt: time.Unix(0, int64(ttl)), + }, + }, + timeNow: timeNow, + parentToChildren: make(map[common.Hash]map[common.Hash]struct{}), + }, + block: &types.Block{ + Header: types.Header{ + Number: 1, + ParentHash: common.Hash{1}, + }, + Body: []types.Extrinsic{[]byte{1}}, + }, + expectedDisjointBlockSet: &disjointBlockSet{ + limit: 1, + blocks: map[common.Hash]*pendingBlock{ + hashHeader(types.Header{Number: 1, ParentHash: common.Hash{1}}): { + hash: hashHeader(types.Header{Number: 1, ParentHash: common.Hash{1}}), + number: 1, + header: setHashToHeader(types.Header{Number: 1, ParentHash: common.Hash{1}}), + body: &types.Body{{1}}, + justification: nil, + clearAt: time.Unix(0, int64(ttl)), + }, + }, + parentToChildren: map[common.Hash]map[common.Hash]struct{}{}, + }, + }, + } + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + t.Parallel() + err := tt.disjointBlockSet.addBlock(tt.block) + if tt.err != nil { + assert.EqualError(t, err, tt.err.Error()) + } else { + assert.NoError(t, err) + } + + tt.disjointBlockSet.timeNow = nil + assert.Equal(t, tt.expectedDisjointBlockSet, tt.disjointBlockSet) + }) + } +} + +func Test_disjointBlockSet_addHeader(t *testing.T) { + t.Parallel() + + hashHeader := func(header types.Header) common.Hash { + return header.Hash() + } + setHashToHeader := func(header types.Header) *types.Header { + header.Hash() + return &header + } + + tests := map[string]struct { + disjointBlockSet *disjointBlockSet + header *types.Header + expectedDisjointBlockSet *disjointBlockSet + err error + }{ + "add header beyond capactiy": { + disjointBlockSet: &disjointBlockSet{}, + header: &types.Header{ + Number: 1, + }, + expectedDisjointBlockSet: &disjointBlockSet{}, + err: errors.New("cannot add block; set is at capacity"), + }, + "add header": { + disjointBlockSet: &disjointBlockSet{ + blocks: make(map[common.Hash]*pendingBlock), + limit: 1, + timeNow: func() time.Time { return time.Unix(0, 0) }, + parentToChildren: make(map[common.Hash]map[common.Hash]struct{}), + }, + header: &types.Header{ + Number: 1, + ParentHash: common.Hash{1}, + }, + expectedDisjointBlockSet: &disjointBlockSet{ + limit: 1, + blocks: map[common.Hash]*pendingBlock{ + hashHeader(types.Header{Number: 1, ParentHash: common.Hash{1}}): { + hash: hashHeader(types.Header{Number: 1, ParentHash: common.Hash{1}}), + number: 1, + header: setHashToHeader(types.Header{Number: 1, ParentHash: common.Hash{1}}), + clearAt: time.Unix(0, int64(ttl)), + }, + }, + parentToChildren: map[common.Hash]map[common.Hash]struct{}{ + {1}: { + hashHeader(types.Header{Number: 1, ParentHash: common.Hash{1}}): {}, + }, + }, + }, + }, + "has header": { + disjointBlockSet: &disjointBlockSet{ + blocks: map[common.Hash]*pendingBlock{ + hashHeader(types.Header{Number: 1, ParentHash: common.Hash{1}}): { + hash: hashHeader(types.Header{Number: 1, ParentHash: common.Hash{1}}), + number: 1, + header: setHashToHeader(types.Header{Number: 1, ParentHash: common.Hash{1}}), + clearAt: time.Unix(0, int64(ttl)), + }, + }, + limit: 1, + timeNow: func() time.Time { return time.Unix(0, 0) }, + parentToChildren: make(map[common.Hash]map[common.Hash]struct{}), + }, + header: &types.Header{ + Number: 1, + ParentHash: common.Hash{1}, + }, + expectedDisjointBlockSet: &disjointBlockSet{ + limit: 1, + blocks: map[common.Hash]*pendingBlock{ + hashHeader(types.Header{Number: 1, ParentHash: common.Hash{1}}): { + hash: hashHeader(types.Header{Number: 1, ParentHash: common.Hash{1}}), + number: 1, + header: setHashToHeader(types.Header{Number: 1, ParentHash: common.Hash{1}}), + clearAt: time.Unix(0, int64(ttl)), + }, + }, + parentToChildren: map[common.Hash]map[common.Hash]struct{}{}, + }, + }, + } + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + t.Parallel() + err := tt.disjointBlockSet.addHeader(tt.header) + if tt.err != nil { + assert.EqualError(t, err, tt.err.Error()) + } else { + assert.NoError(t, err) + } + + tt.disjointBlockSet.timeNow = nil + assert.Equal(t, tt.expectedDisjointBlockSet, tt.disjointBlockSet) + }) + } +} + +func Test_disjointBlockSet_clearBlocks(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + disjointBlockSet *disjointBlockSet + remaining map[common.Hash]*pendingBlock + }{ + { + name: "base case", + disjointBlockSet: &disjointBlockSet{ + limit: 0, + blocks: map[common.Hash]*pendingBlock{ + {1}: { + clearAt: time.Unix(1000, 0), + hash: common.Hash{1}, + }, + }, + timeNow: func() time.Time { return time.Unix(1001, 0) }, + }, + remaining: map[common.Hash]*pendingBlock{}, + }, + { + name: "remove clear one block", + disjointBlockSet: &disjointBlockSet{ + limit: 0, + blocks: map[common.Hash]*pendingBlock{ + {1}: { + clearAt: time.Unix(1000, 0), + hash: common.Hash{1}, + }, + {2}: { + clearAt: time.Unix(1002, 0), + hash: common.Hash{2}, + }, + }, + timeNow: func() time.Time { return time.Unix(1001, 0) }, + }, + remaining: map[common.Hash]*pendingBlock{ + {2}: { + clearAt: time.Unix(1002, 0), + hash: common.Hash{2}, + }, + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + tt.disjointBlockSet.clearBlocks() + assert.Equal(t, tt.remaining, tt.disjointBlockSet.blocks) + }) + } +} + +func Test_disjointBlockSet_getBlocks(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + disjointBlockSet *disjointBlockSet + want []*pendingBlock + wantDisjointBlockSet *disjointBlockSet + }{ + { + name: "no blocks", + disjointBlockSet: &disjointBlockSet{}, + want: []*pendingBlock{}, + wantDisjointBlockSet: &disjointBlockSet{}, + }, + { + name: "base case", + disjointBlockSet: &disjointBlockSet{ + blocks: map[common.Hash]*pendingBlock{ + {}: {}, + }, + }, + want: []*pendingBlock{{}}, + wantDisjointBlockSet: &disjointBlockSet{ + blocks: map[common.Hash]*pendingBlock{ + {}: {}, + }, + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + blocks := tt.disjointBlockSet.getBlocks() + assert.Equal(t, tt.want, blocks) + assert.Equal(t, tt.wantDisjointBlockSet, tt.disjointBlockSet) + }) + } +} + +func Test_disjointBlockSet_removeLowerBlocks(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + disjointBlockSet *disjointBlockSet + num uint + remaining map[common.Hash]*pendingBlock + wantDisjointBlockSet *disjointBlockSet + }{ + { + name: "number 0", + disjointBlockSet: &disjointBlockSet{ + blocks: map[common.Hash]*pendingBlock{ + {1}: { + hash: common.Hash{1}, + number: 1, + }, + {10}: { + hash: common.Hash{10}, + number: 10, + }, + }, + }, + num: 0, + remaining: map[common.Hash]*pendingBlock{ + {1}: { + hash: common.Hash{1}, + number: 1, + }, + {10}: { + hash: common.Hash{10}, + number: 10, + }, + }, + wantDisjointBlockSet: &disjointBlockSet{ + blocks: map[common.Hash]*pendingBlock{ + {1}: { + hash: common.Hash{1}, + number: 1, + }, + {10}: { + hash: common.Hash{10}, + number: 10, + }, + }, + }, + }, + { + name: "number 1", + disjointBlockSet: &disjointBlockSet{ + blocks: map[common.Hash]*pendingBlock{ + {1}: { + hash: common.Hash{1}, + number: 1, + }, + {10}: { + hash: common.Hash{10}, + number: 10, + }, + }, + }, + num: 1, + remaining: map[common.Hash]*pendingBlock{{10}: { + hash: common.Hash{10}, + number: 10, + }, + }, + wantDisjointBlockSet: &disjointBlockSet{ + blocks: map[common.Hash]*pendingBlock{ + {10}: { + hash: common.Hash{10}, + number: 10, + }, + }, + }, + }, + { + name: "number 11", + disjointBlockSet: &disjointBlockSet{ + blocks: map[common.Hash]*pendingBlock{ + {1}: { + hash: common.Hash{1}, + number: 1, + }, + {10}: { + hash: common.Hash{10}, + number: 10, + }, + }, + }, + num: 11, + remaining: map[common.Hash]*pendingBlock{}, + wantDisjointBlockSet: &disjointBlockSet{ + blocks: map[common.Hash]*pendingBlock{}, + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + tt.disjointBlockSet.removeLowerBlocks(tt.num) + assert.Equal(t, tt.remaining, tt.disjointBlockSet.blocks) + assert.Equal(t, tt.wantDisjointBlockSet, tt.disjointBlockSet) + }) + } +} + +func Test_disjointBlockSet_size(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + disjointBlockSet *disjointBlockSet + want int + }{ + { + name: "expect 0", + disjointBlockSet: &disjointBlockSet{ + blocks: map[common.Hash]*pendingBlock{}, + }, + want: 0, + }, + { + name: "expect 1", + disjointBlockSet: &disjointBlockSet{ + blocks: map[common.Hash]*pendingBlock{ + {1}: {hash: common.Hash{1}, number: 1}, + }, + }, + want: 1, + }, + { + name: "expect 2", + disjointBlockSet: &disjointBlockSet{ + blocks: map[common.Hash]*pendingBlock{ + {1}: {hash: common.Hash{1}, number: 1}, + {10}: {hash: common.Hash{10}, number: 10}, + }, + }, + want: 2, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + size := tt.disjointBlockSet.size() + assert.Equal(t, tt.want, size) + }) + } +} diff --git a/dot/sync/interface.go b/dot/sync/interface.go index 7050aa827d..4922a02b12 100644 --- a/dot/sync/interface.go +++ b/dot/sync/interface.go @@ -15,7 +15,10 @@ import ( "github.com/libp2p/go-libp2p-core/peer" ) +// TODO: replace usage of mockery generated mocks with mockgen generated mocks. +// Note: This mockery go:generate is still being used //go:generate mockery --name BlockState --structname BlockState --case underscore --keeptree +//go:generate mockgen -destination=mock_interface_test.go -package=$GOPACKAGE . BlockState,StorageState,CodeSubstitutedState,TransactionState,BabeVerifier,FinalityGadget,BlockImportHandler,Network // BlockState is the interface for the block state type BlockState interface { @@ -66,29 +69,21 @@ type TransactionState interface { RemoveExtrinsic(ext types.Extrinsic) } -//go:generate mockery --name BabeVerifier --structname BabeVerifier --case underscore --keeptree - // BabeVerifier deals with BABE block verification type BabeVerifier interface { VerifyBlock(header *types.Header) error } -//go:generate mockery --name FinalityGadget --structname FinalityGadget --case underscore --keeptree - // FinalityGadget implements justification verification functionality type FinalityGadget interface { VerifyBlockJustification(common.Hash, []byte) error } -//go:generate mockery --name BlockImportHandler --structname BlockImportHandler --case underscore --keeptree - // BlockImportHandler is the interface for the handler of newly imported blocks type BlockImportHandler interface { HandleBlockImport(block *types.Block, state *rtstorage.TrieState) error } -//go:generate mockery --name Network --structname Network --case underscore --keeptree - // Network is the interface for the network type Network interface { // DoBlockRequest sends a request to the given peer. diff --git a/dot/sync/message_integeration_test.go b/dot/sync/message_integeration_test.go index ac0f34f096..3db0809306 100644 --- a/dot/sync/message_integeration_test.go +++ b/dot/sync/message_integeration_test.go @@ -364,7 +364,7 @@ func TestService_CreateBlockResponse_Descending_EndHash(t *testing.T) { require.Equal(t, uint(1), resp.BlockData[127].Number()) } -func TestService_checkOrGetDescendantHash(t *testing.T) { +func TestService_checkOrGetDescendantHash_integeration(t *testing.T) { t.Parallel() s := newTestSyncer(t) branches := map[uint]int{ diff --git a/dot/sync/message_test.go b/dot/sync/message_test.go new file mode 100644 index 0000000000..5853aa5e8a --- /dev/null +++ b/dot/sync/message_test.go @@ -0,0 +1,430 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package sync + +import ( + "errors" + "testing" + + "github.com/ChainSafe/gossamer/dot/network" + "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/common/variadic" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" +) + +func TestService_CreateBlockResponse(t *testing.T) { + t.Parallel() + + type args struct { + req *network.BlockRequestMessage + } + tests := map[string]struct { + blockStateBuilder func(ctrl *gomock.Controller) BlockState + args args + want *network.BlockResponseMessage + err error + }{ + "invalid block request": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + return nil + }, + args: args{req: &network.BlockRequestMessage{}}, + err: ErrInvalidBlockRequest, + }, + "ascending request nil startHash nil endHash": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().BestBlockNumber().Return(uint(1), nil).Times(2) + mockBlockState.EXPECT().GetHashByNumber(uint(1)).Return(common.Hash{1, 2}, nil) + return mockBlockState + }, + args: args{req: &network.BlockRequestMessage{ + StartingBlock: *variadic.MustNewUint32OrHash(0), + Direction: network.Ascending, + }}, + want: &network.BlockResponseMessage{BlockData: []*types.BlockData{{ + Hash: common.Hash{1, 2}, + }}}, + }, + "ascending request start number higher": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().BestBlockNumber().Return(uint(1), nil) + return mockBlockState + }, + + args: args{req: &network.BlockRequestMessage{ + StartingBlock: *variadic.MustNewUint32OrHash(2), + Direction: network.Ascending, + }}, + err: errRequestStartTooHigh, + want: nil, + }, + "ascending request endHash not nil": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().BestBlockNumber().Return(uint(1), nil) + mockBlockState.EXPECT().GetHashByNumber(uint(1)).Return(common.Hash{1, 2}, nil) + mockBlockState.EXPECT().IsDescendantOf(common.Hash{1, 2}, common.Hash{1, 2, 3}).Return(true, + nil).Times(2) + mockBlockState.EXPECT().GetHeader(common.Hash{1, 2}).Return(&types.Header{ + Number: 1, + }, nil) + mockBlockState.EXPECT().GetHeader(common.Hash{1, 2, 3}).Return(&types.Header{ + Number: 2, + }, nil) + mockBlockState.EXPECT().SubChain(common.Hash{1, 2}, common.Hash{1, 2, 3}).Return([]common.Hash{{1, 2}}, + nil) + return mockBlockState + }, + args: args{req: &network.BlockRequestMessage{ + StartingBlock: *variadic.MustNewUint32OrHash(0), + EndBlockHash: &common.Hash{1, 2, 3}, + Direction: network.Ascending, + }}, + want: &network.BlockResponseMessage{BlockData: []*types.BlockData{{ + Hash: common.Hash{1, 2}, + }}}, + }, + "descending request nil startHash nil endHash": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().BestBlockNumber().Return(uint(1), nil) + return mockBlockState + }, + args: args{req: &network.BlockRequestMessage{ + StartingBlock: *variadic.MustNewUint32OrHash(0), + Direction: network.Descending, + }}, + want: &network.BlockResponseMessage{BlockData: []*types.BlockData{}}, + }, + "descending request start number higher": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().BestBlockNumber().Return(uint(1), nil) + mockBlockState.EXPECT().GetHashByNumber(uint(1)).Return(common.Hash{1, 2}, nil) + return mockBlockState + }, + args: args{req: &network.BlockRequestMessage{ + StartingBlock: *variadic.MustNewUint32OrHash(2), + Direction: network.Descending, + }}, + err: nil, + want: &network.BlockResponseMessage{BlockData: []*types.BlockData{{ + Hash: common.Hash{1, 2}, + }}}, + }, + "descending request endHash not nil": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().BestBlockNumber().Return(uint(1), nil) + mockBlockState.EXPECT().GetHashByNumber(uint(0)).Return(common.Hash{1, 2}, nil) + mockBlockState.EXPECT().IsDescendantOf(common.Hash{1, 2, 3}, common.Hash{1, 2}).Return(true, + nil) + mockBlockState.EXPECT().SubChain(common.Hash{1, 2, 3}, common.Hash{1, 2}).Return([]common.Hash{{1, + 2}}, + nil) + return mockBlockState + }, + args: args{req: &network.BlockRequestMessage{ + StartingBlock: *variadic.MustNewUint32OrHash(0), + EndBlockHash: &common.Hash{1, 2, 3}, + Direction: network.Descending, + }}, + want: &network.BlockResponseMessage{BlockData: []*types.BlockData{{ + Hash: common.Hash{1, 2}, + }}}, + }, + "ascending request startHash nil endHash": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetHeader(common.Hash{}).Return(&types.Header{ + Number: 1, + }, nil) + mockBlockState.EXPECT().BestBlockNumber().Return(uint(2), nil) + mockBlockState.EXPECT().GetHashByNumber(uint(2)).Return(common.Hash{1, 2, 3}, nil) + mockBlockState.EXPECT().IsDescendantOf(common.Hash{}, common.Hash{1, 2, 3}).Return(true, + nil) + mockBlockState.EXPECT().SubChain(common.Hash{}, common.Hash{1, 2, 3}).Return([]common.Hash{{1, + 2}}, + nil) + return mockBlockState + }, + args: args{req: &network.BlockRequestMessage{ + StartingBlock: *variadic.MustNewUint32OrHash(common.Hash{}), + Direction: network.Ascending, + }}, + want: &network.BlockResponseMessage{BlockData: []*types.BlockData{{ + Hash: common.Hash{1, 2}, + }}}, + }, + "descending request startHash nil endHash": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetHeader(common.Hash{}).Return(&types.Header{ + Number: 1, + }, nil) + mockBlockState.EXPECT().GetHeaderByNumber(uint(1)).Return(&types.Header{ + Number: 1, + }, nil) + mockBlockState.EXPECT().SubChain(common.MustHexToHash( + "0x6443a0b46e0412e626363028115a9f2cf963eeed526b8b33e5316f08b50d0dc3"), + common.Hash{}).Return([]common.Hash{{1, 2}}, nil) + return mockBlockState + }, + args: args{req: &network.BlockRequestMessage{ + StartingBlock: *variadic.MustNewUint32OrHash(common.Hash{}), + Direction: network.Descending, + }}, + want: &network.BlockResponseMessage{BlockData: []*types.BlockData{{ + Hash: common.Hash{1, 2}, + }}}, + }, + "invalid direction": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + return nil + }, + args: args{req: &network.BlockRequestMessage{ + Direction: network.SyncDirection(3), + }}, + err: errInvalidRequestDirection, + }, + } + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + s := &Service{ + blockState: tt.blockStateBuilder(ctrl), + } + got, err := s.CreateBlockResponse(tt.args.req) + if tt.err != nil { + assert.EqualError(t, err, tt.err.Error()) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tt.want, got) + }) + } +} + +func TestService_checkOrGetDescendantHash(t *testing.T) { + t.Parallel() + + type args struct { + ancestor common.Hash + descendant *common.Hash + descendantNumber uint + } + tests := map[string]struct { + name string + blockStateBuilder func(ctrl *gomock.Controller) BlockState + args args + want common.Hash + expectedError error + }{ + "nil descendant": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockStateBuilder := NewMockBlockState(ctrl) + mockStateBuilder.EXPECT().GetHashByNumber(uint(1)).Return(common.Hash{}, nil) + mockStateBuilder.EXPECT().IsDescendantOf(common.Hash{}, common.Hash{}).Return(true, nil) + return mockStateBuilder + }, + args: args{ancestor: common.Hash{}, descendant: nil, descendantNumber: 1}, + }, + "not nil descendant": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetHeader(common.Hash{}).Return(&types.Header{}, nil) + mockBlockState.EXPECT().IsDescendantOf(common.Hash{}, common.Hash{1, 2}).Return(true, nil) + return mockBlockState + }, + args: args{ancestor: common.Hash{0}, descendant: &common.Hash{1, 2}, descendantNumber: 1}, + want: common.Hash{1, 2}, + }, + "descendant greater than header": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetHeader(common.Hash{2}).Return(&types.Header{ + Number: 2, + }, nil) + return mockBlockState + }, + args: args{ancestor: common.Hash{2}, descendant: &common.Hash{1, 2}, descendantNumber: 1}, + want: common.Hash{}, + expectedError: errors.New("invalid request, descendant number 2 is higher than ancestor 1"), + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + s := &Service{ + blockState: tt.blockStateBuilder(ctrl), + } + got, err := s.checkOrGetDescendantHash(tt.args.ancestor, tt.args.descendant, tt.args.descendantNumber) + if tt.expectedError != nil { + assert.EqualError(t, err, tt.expectedError.Error()) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tt.want, got) + }) + } +} + +func TestService_getBlockData(t *testing.T) { + t.Parallel() + + type args struct { + hash common.Hash + requestedData byte + } + tests := map[string]struct { + blockStateBuilder func(ctrl *gomock.Controller) BlockState + args args + want *types.BlockData + err error + }{ + "requestedData 0": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + return nil + }, + args: args{ + hash: common.Hash{}, + requestedData: 0, + }, + want: &types.BlockData{}, + }, + "requestedData RequestedDataHeader error": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetHeader(common.Hash{}).Return(nil, errors.New("empty hash")) + return mockBlockState + }, + args: args{ + hash: common.Hash{0}, + requestedData: network.RequestedDataHeader, + }, + want: &types.BlockData{ + Hash: common.Hash{}, + }, + }, + "requestedData RequestedDataHeader": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetHeader(common.Hash{1}).Return(&types.Header{ + Number: 2, + }, nil) + return mockBlockState + }, + args: args{ + hash: common.Hash{1}, + requestedData: network.RequestedDataHeader, + }, + want: &types.BlockData{ + Hash: common.Hash{1}, + Header: &types.Header{ + Number: 2, + }, + }, + }, + "requestedData RequestedDataBody error": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetBlockBody(common.Hash{}).Return(nil, errors.New("empty hash")) + return mockBlockState + }, + + args: args{ + hash: common.Hash{}, + requestedData: network.RequestedDataBody, + }, + want: &types.BlockData{ + Hash: common.Hash{}, + }, + }, + "requestedData RequestedDataBody": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetBlockBody(common.Hash{1}).Return(&types.Body{[]byte{1}}, nil) + return mockBlockState + }, + args: args{ + hash: common.Hash{1}, + requestedData: network.RequestedDataBody, + }, + want: &types.BlockData{ + Hash: common.Hash{1}, + Body: &types.Body{[]byte{1}}, + }, + }, + "requestedData RequestedDataReceipt": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetReceipt(common.Hash{1}).Return([]byte{1}, nil) + return mockBlockState + }, + args: args{ + hash: common.Hash{1}, + requestedData: network.RequestedDataReceipt, + }, + want: &types.BlockData{ + Hash: common.Hash{1}, + Receipt: &[]byte{1}, + }, + }, + "requestedData RequestedDataMessageQueue": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetMessageQueue(common.Hash{2}).Return([]byte{2}, nil) + return mockBlockState + }, + args: args{ + hash: common.Hash{2}, + requestedData: network.RequestedDataMessageQueue, + }, + want: &types.BlockData{ + Hash: common.Hash{2}, + MessageQueue: &[]byte{2}, + }, + }, + "requestedData RequestedDataJustification": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetJustification(common.Hash{3}).Return([]byte{3}, nil) + return mockBlockState + }, + args: args{ + hash: common.Hash{3}, + requestedData: network.RequestedDataJustification, + }, + want: &types.BlockData{ + Hash: common.Hash{3}, + Justification: &[]byte{3}, + }, + }, + } + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + s := &Service{ + blockState: tt.blockStateBuilder(ctrl), + } + got, err := s.getBlockData(tt.args.hash, tt.args.requestedData) + if tt.err != nil { + assert.EqualError(t, err, tt.err.Error()) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/dot/sync/mock_chain_processor_test.go b/dot/sync/mock_chain_processor_test.go new file mode 100644 index 0000000000..5eeaa0d450 --- /dev/null +++ b/dot/sync/mock_chain_processor_test.go @@ -0,0 +1,58 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ChainSafe/gossamer/dot/sync (interfaces: ChainProcessor) + +// Package sync is a generated GoMock package. +package sync + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockChainProcessor is a mock of ChainProcessor interface. +type MockChainProcessor struct { + ctrl *gomock.Controller + recorder *MockChainProcessorMockRecorder +} + +// MockChainProcessorMockRecorder is the mock recorder for MockChainProcessor. +type MockChainProcessorMockRecorder struct { + mock *MockChainProcessor +} + +// NewMockChainProcessor creates a new mock instance. +func NewMockChainProcessor(ctrl *gomock.Controller) *MockChainProcessor { + mock := &MockChainProcessor{ctrl: ctrl} + mock.recorder = &MockChainProcessorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockChainProcessor) EXPECT() *MockChainProcessorMockRecorder { + return m.recorder +} + +// start mocks base method. +func (m *MockChainProcessor) start() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "start") +} + +// start indicates an expected call of start. +func (mr *MockChainProcessorMockRecorder) start() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "start", reflect.TypeOf((*MockChainProcessor)(nil).start)) +} + +// stop mocks base method. +func (m *MockChainProcessor) stop() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "stop") +} + +// stop indicates an expected call of stop. +func (mr *MockChainProcessorMockRecorder) stop() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "stop", reflect.TypeOf((*MockChainProcessor)(nil).stop)) +} diff --git a/dot/sync/mock_chain_sync_test.go b/dot/sync/mock_chain_sync_test.go index 43f15db810..cf4d4c3c71 100644 --- a/dot/sync/mock_chain_sync_test.go +++ b/dot/sync/mock_chain_sync_test.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ChainSafe/gossamer/dot/sync (interfaces: ChainSync) +// Source: chain_sync.go // Package sync is a generated GoMock package. package sync @@ -13,6 +13,88 @@ import ( peer "github.com/libp2p/go-libp2p-core/peer" ) +// MockworkHandler is a mock of workHandler interface. +type MockworkHandler struct { + ctrl *gomock.Controller + recorder *MockworkHandlerMockRecorder +} + +// MockworkHandlerMockRecorder is the mock recorder for MockworkHandler. +type MockworkHandlerMockRecorder struct { + mock *MockworkHandler +} + +// NewMockworkHandler creates a new mock instance. +func NewMockworkHandler(ctrl *gomock.Controller) *MockworkHandler { + mock := &MockworkHandler{ctrl: ctrl} + mock.recorder = &MockworkHandlerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockworkHandler) EXPECT() *MockworkHandlerMockRecorder { + return m.recorder +} + +// handleNewPeerState mocks base method. +func (m *MockworkHandler) handleNewPeerState(arg0 *peerState) (*worker, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "handleNewPeerState", arg0) + ret0, _ := ret[0].(*worker) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// handleNewPeerState indicates an expected call of handleNewPeerState. +func (mr *MockworkHandlerMockRecorder) handleNewPeerState(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "handleNewPeerState", reflect.TypeOf((*MockworkHandler)(nil).handleNewPeerState), arg0) +} + +// handleTick mocks base method. +func (m *MockworkHandler) handleTick() ([]*worker, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "handleTick") + ret0, _ := ret[0].([]*worker) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// handleTick indicates an expected call of handleTick. +func (mr *MockworkHandlerMockRecorder) handleTick() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "handleTick", reflect.TypeOf((*MockworkHandler)(nil).handleTick)) +} + +// handleWorkerResult mocks base method. +func (m *MockworkHandler) handleWorkerResult(w *worker) (*worker, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "handleWorkerResult", w) + ret0, _ := ret[0].(*worker) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// handleWorkerResult indicates an expected call of handleWorkerResult. +func (mr *MockworkHandlerMockRecorder) handleWorkerResult(w interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "handleWorkerResult", reflect.TypeOf((*MockworkHandler)(nil).handleWorkerResult), w) +} + +// hasCurrentWorker mocks base method. +func (m *MockworkHandler) hasCurrentWorker(arg0 *worker, arg1 map[uint64]*worker) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "hasCurrentWorker", arg0, arg1) + ret0, _ := ret[0].(bool) + return ret0 +} + +// hasCurrentWorker indicates an expected call of hasCurrentWorker. +func (mr *MockworkHandlerMockRecorder) hasCurrentWorker(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "hasCurrentWorker", reflect.TypeOf((*MockworkHandler)(nil).hasCurrentWorker), arg0, arg1) +} + // MockChainSync is a mock of ChainSync interface. type MockChainSync struct { ctrl *gomock.Controller @@ -52,31 +134,31 @@ func (mr *MockChainSyncMockRecorder) getHighestBlock() *gomock.Call { } // setBlockAnnounce mocks base method. -func (m *MockChainSync) setBlockAnnounce(arg0 peer.ID, arg1 *types.Header) error { +func (m *MockChainSync) setBlockAnnounce(from peer.ID, header *types.Header) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "setBlockAnnounce", arg0, arg1) + ret := m.ctrl.Call(m, "setBlockAnnounce", from, header) ret0, _ := ret[0].(error) return ret0 } // setBlockAnnounce indicates an expected call of setBlockAnnounce. -func (mr *MockChainSyncMockRecorder) setBlockAnnounce(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockChainSyncMockRecorder) setBlockAnnounce(from, header interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "setBlockAnnounce", reflect.TypeOf((*MockChainSync)(nil).setBlockAnnounce), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "setBlockAnnounce", reflect.TypeOf((*MockChainSync)(nil).setBlockAnnounce), from, header) } // setPeerHead mocks base method. -func (m *MockChainSync) setPeerHead(arg0 peer.ID, arg1 common.Hash, arg2 uint) error { +func (m *MockChainSync) setPeerHead(p peer.ID, hash common.Hash, number uint) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "setPeerHead", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "setPeerHead", p, hash, number) ret0, _ := ret[0].(error) return ret0 } // setPeerHead indicates an expected call of setPeerHead. -func (mr *MockChainSyncMockRecorder) setPeerHead(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockChainSyncMockRecorder) setPeerHead(p, hash, number interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "setPeerHead", reflect.TypeOf((*MockChainSync)(nil).setPeerHead), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "setPeerHead", reflect.TypeOf((*MockChainSync)(nil).setPeerHead), p, hash, number) } // start mocks base method. diff --git a/dot/sync/mock_disjoint_block_set_test.go b/dot/sync/mock_disjoint_block_set_test.go new file mode 100644 index 0000000000..48e791701e --- /dev/null +++ b/dot/sync/mock_disjoint_block_set_test.go @@ -0,0 +1,212 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ChainSafe/gossamer/dot/sync (interfaces: DisjointBlockSet) + +// Package sync is a generated GoMock package. +package sync + +import ( + reflect "reflect" + + types "github.com/ChainSafe/gossamer/dot/types" + common "github.com/ChainSafe/gossamer/lib/common" + gomock "github.com/golang/mock/gomock" +) + +// MockDisjointBlockSet is a mock of DisjointBlockSet interface. +type MockDisjointBlockSet struct { + ctrl *gomock.Controller + recorder *MockDisjointBlockSetMockRecorder +} + +// MockDisjointBlockSetMockRecorder is the mock recorder for MockDisjointBlockSet. +type MockDisjointBlockSetMockRecorder struct { + mock *MockDisjointBlockSet +} + +// NewMockDisjointBlockSet creates a new mock instance. +func NewMockDisjointBlockSet(ctrl *gomock.Controller) *MockDisjointBlockSet { + mock := &MockDisjointBlockSet{ctrl: ctrl} + mock.recorder = &MockDisjointBlockSetMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDisjointBlockSet) EXPECT() *MockDisjointBlockSetMockRecorder { + return m.recorder +} + +// addBlock mocks base method. +func (m *MockDisjointBlockSet) addBlock(arg0 *types.Block) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "addBlock", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// addBlock indicates an expected call of addBlock. +func (mr *MockDisjointBlockSetMockRecorder) addBlock(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "addBlock", reflect.TypeOf((*MockDisjointBlockSet)(nil).addBlock), arg0) +} + +// addHashAndNumber mocks base method. +func (m *MockDisjointBlockSet) addHashAndNumber(arg0 common.Hash, arg1 uint) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "addHashAndNumber", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// addHashAndNumber indicates an expected call of addHashAndNumber. +func (mr *MockDisjointBlockSetMockRecorder) addHashAndNumber(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "addHashAndNumber", reflect.TypeOf((*MockDisjointBlockSet)(nil).addHashAndNumber), arg0, arg1) +} + +// addHeader mocks base method. +func (m *MockDisjointBlockSet) addHeader(arg0 *types.Header) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "addHeader", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// addHeader indicates an expected call of addHeader. +func (mr *MockDisjointBlockSetMockRecorder) addHeader(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "addHeader", reflect.TypeOf((*MockDisjointBlockSet)(nil).addHeader), arg0) +} + +// addJustification mocks base method. +func (m *MockDisjointBlockSet) addJustification(arg0 common.Hash, arg1 []byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "addJustification", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// addJustification indicates an expected call of addJustification. +func (mr *MockDisjointBlockSetMockRecorder) addJustification(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "addJustification", reflect.TypeOf((*MockDisjointBlockSet)(nil).addJustification), arg0, arg1) +} + +// getBlock mocks base method. +func (m *MockDisjointBlockSet) getBlock(arg0 common.Hash) *pendingBlock { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "getBlock", arg0) + ret0, _ := ret[0].(*pendingBlock) + return ret0 +} + +// getBlock indicates an expected call of getBlock. +func (mr *MockDisjointBlockSetMockRecorder) getBlock(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getBlock", reflect.TypeOf((*MockDisjointBlockSet)(nil).getBlock), arg0) +} + +// getBlocks mocks base method. +func (m *MockDisjointBlockSet) getBlocks() []*pendingBlock { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "getBlocks") + ret0, _ := ret[0].([]*pendingBlock) + return ret0 +} + +// getBlocks indicates an expected call of getBlocks. +func (mr *MockDisjointBlockSetMockRecorder) getBlocks() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getBlocks", reflect.TypeOf((*MockDisjointBlockSet)(nil).getBlocks)) +} + +// getChildren mocks base method. +func (m *MockDisjointBlockSet) getChildren(arg0 common.Hash) map[common.Hash]struct{} { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "getChildren", arg0) + ret0, _ := ret[0].(map[common.Hash]struct{}) + return ret0 +} + +// getChildren indicates an expected call of getChildren. +func (mr *MockDisjointBlockSetMockRecorder) getChildren(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getChildren", reflect.TypeOf((*MockDisjointBlockSet)(nil).getChildren), arg0) +} + +// getReadyDescendants mocks base method. +func (m *MockDisjointBlockSet) getReadyDescendants(arg0 common.Hash, arg1 []*types.BlockData) []*types.BlockData { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "getReadyDescendants", arg0, arg1) + ret0, _ := ret[0].([]*types.BlockData) + return ret0 +} + +// getReadyDescendants indicates an expected call of getReadyDescendants. +func (mr *MockDisjointBlockSetMockRecorder) getReadyDescendants(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getReadyDescendants", reflect.TypeOf((*MockDisjointBlockSet)(nil).getReadyDescendants), arg0, arg1) +} + +// hasBlock mocks base method. +func (m *MockDisjointBlockSet) hasBlock(arg0 common.Hash) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "hasBlock", arg0) + ret0, _ := ret[0].(bool) + return ret0 +} + +// hasBlock indicates an expected call of hasBlock. +func (mr *MockDisjointBlockSetMockRecorder) hasBlock(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "hasBlock", reflect.TypeOf((*MockDisjointBlockSet)(nil).hasBlock), arg0) +} + +// removeBlock mocks base method. +func (m *MockDisjointBlockSet) removeBlock(arg0 common.Hash) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "removeBlock", arg0) +} + +// removeBlock indicates an expected call of removeBlock. +func (mr *MockDisjointBlockSetMockRecorder) removeBlock(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "removeBlock", reflect.TypeOf((*MockDisjointBlockSet)(nil).removeBlock), arg0) +} + +// removeLowerBlocks mocks base method. +func (m *MockDisjointBlockSet) removeLowerBlocks(arg0 uint) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "removeLowerBlocks", arg0) +} + +// removeLowerBlocks indicates an expected call of removeLowerBlocks. +func (mr *MockDisjointBlockSetMockRecorder) removeLowerBlocks(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "removeLowerBlocks", reflect.TypeOf((*MockDisjointBlockSet)(nil).removeLowerBlocks), arg0) +} + +// run mocks base method. +func (m *MockDisjointBlockSet) run(arg0 <-chan struct{}) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "run", arg0) +} + +// run indicates an expected call of run. +func (mr *MockDisjointBlockSetMockRecorder) run(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "run", reflect.TypeOf((*MockDisjointBlockSet)(nil).run), arg0) +} + +// size mocks base method. +func (m *MockDisjointBlockSet) size() int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "size") + ret0, _ := ret[0].(int) + return ret0 +} + +// size indicates an expected call of size. +func (mr *MockDisjointBlockSetMockRecorder) size() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "size", reflect.TypeOf((*MockDisjointBlockSet)(nil).size)) +} diff --git a/dot/sync/mock_instance_test.go b/dot/sync/mock_instance_test.go new file mode 100644 index 0000000000..ce3af4cb60 --- /dev/null +++ b/dot/sync/mock_instance_test.go @@ -0,0 +1,404 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ChainSafe/gossamer/lib/runtime (interfaces: Instance) + +// Package sync is a generated GoMock package. +package sync + +import ( + reflect "reflect" + + types "github.com/ChainSafe/gossamer/dot/types" + common "github.com/ChainSafe/gossamer/lib/common" + keystore "github.com/ChainSafe/gossamer/lib/keystore" + runtime "github.com/ChainSafe/gossamer/lib/runtime" + transaction "github.com/ChainSafe/gossamer/lib/transaction" + gomock "github.com/golang/mock/gomock" +) + +// MockInstance is a mock of Instance interface. +type MockInstance struct { + ctrl *gomock.Controller + recorder *MockInstanceMockRecorder +} + +// MockInstanceMockRecorder is the mock recorder for MockInstance. +type MockInstanceMockRecorder struct { + mock *MockInstance +} + +// NewMockInstance creates a new mock instance. +func NewMockInstance(ctrl *gomock.Controller) *MockInstance { + mock := &MockInstance{ctrl: ctrl} + mock.recorder = &MockInstanceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockInstance) EXPECT() *MockInstanceMockRecorder { + return m.recorder +} + +// ApplyExtrinsic mocks base method. +func (m *MockInstance) ApplyExtrinsic(arg0 types.Extrinsic) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ApplyExtrinsic", arg0) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ApplyExtrinsic indicates an expected call of ApplyExtrinsic. +func (mr *MockInstanceMockRecorder) ApplyExtrinsic(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApplyExtrinsic", reflect.TypeOf((*MockInstance)(nil).ApplyExtrinsic), arg0) +} + +// BabeConfiguration mocks base method. +func (m *MockInstance) BabeConfiguration() (*types.BabeConfiguration, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BabeConfiguration") + ret0, _ := ret[0].(*types.BabeConfiguration) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BabeConfiguration indicates an expected call of BabeConfiguration. +func (mr *MockInstanceMockRecorder) BabeConfiguration() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BabeConfiguration", reflect.TypeOf((*MockInstance)(nil).BabeConfiguration)) +} + +// CheckInherents mocks base method. +func (m *MockInstance) CheckInherents() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "CheckInherents") +} + +// CheckInherents indicates an expected call of CheckInherents. +func (mr *MockInstanceMockRecorder) CheckInherents() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckInherents", reflect.TypeOf((*MockInstance)(nil).CheckInherents)) +} + +// CheckRuntimeVersion mocks base method. +func (m *MockInstance) CheckRuntimeVersion(arg0 []byte) (runtime.Version, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CheckRuntimeVersion", arg0) + ret0, _ := ret[0].(runtime.Version) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CheckRuntimeVersion indicates an expected call of CheckRuntimeVersion. +func (mr *MockInstanceMockRecorder) CheckRuntimeVersion(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckRuntimeVersion", reflect.TypeOf((*MockInstance)(nil).CheckRuntimeVersion), arg0) +} + +// DecodeSessionKeys mocks base method. +func (m *MockInstance) DecodeSessionKeys(arg0 []byte) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DecodeSessionKeys", arg0) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DecodeSessionKeys indicates an expected call of DecodeSessionKeys. +func (mr *MockInstanceMockRecorder) DecodeSessionKeys(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecodeSessionKeys", reflect.TypeOf((*MockInstance)(nil).DecodeSessionKeys), arg0) +} + +// Exec mocks base method. +func (m *MockInstance) Exec(arg0 string, arg1 []byte) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Exec", arg0, arg1) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Exec indicates an expected call of Exec. +func (mr *MockInstanceMockRecorder) Exec(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exec", reflect.TypeOf((*MockInstance)(nil).Exec), arg0, arg1) +} + +// ExecuteBlock mocks base method. +func (m *MockInstance) ExecuteBlock(arg0 *types.Block) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExecuteBlock", arg0) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ExecuteBlock indicates an expected call of ExecuteBlock. +func (mr *MockInstanceMockRecorder) ExecuteBlock(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecuteBlock", reflect.TypeOf((*MockInstance)(nil).ExecuteBlock), arg0) +} + +// FinalizeBlock mocks base method. +func (m *MockInstance) FinalizeBlock() (*types.Header, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FinalizeBlock") + ret0, _ := ret[0].(*types.Header) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FinalizeBlock indicates an expected call of FinalizeBlock. +func (mr *MockInstanceMockRecorder) FinalizeBlock() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FinalizeBlock", reflect.TypeOf((*MockInstance)(nil).FinalizeBlock)) +} + +// GenerateSessionKeys mocks base method. +func (m *MockInstance) GenerateSessionKeys() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "GenerateSessionKeys") +} + +// GenerateSessionKeys indicates an expected call of GenerateSessionKeys. +func (mr *MockInstanceMockRecorder) GenerateSessionKeys() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GenerateSessionKeys", reflect.TypeOf((*MockInstance)(nil).GenerateSessionKeys)) +} + +// GetCodeHash mocks base method. +func (m *MockInstance) GetCodeHash() common.Hash { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCodeHash") + ret0, _ := ret[0].(common.Hash) + return ret0 +} + +// GetCodeHash indicates an expected call of GetCodeHash. +func (mr *MockInstanceMockRecorder) GetCodeHash() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCodeHash", reflect.TypeOf((*MockInstance)(nil).GetCodeHash)) +} + +// GrandpaAuthorities mocks base method. +func (m *MockInstance) GrandpaAuthorities() ([]types.Authority, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GrandpaAuthorities") + ret0, _ := ret[0].([]types.Authority) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GrandpaAuthorities indicates an expected call of GrandpaAuthorities. +func (mr *MockInstanceMockRecorder) GrandpaAuthorities() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrandpaAuthorities", reflect.TypeOf((*MockInstance)(nil).GrandpaAuthorities)) +} + +// InherentExtrinsics mocks base method. +func (m *MockInstance) InherentExtrinsics(arg0 []byte) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InherentExtrinsics", arg0) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// InherentExtrinsics indicates an expected call of InherentExtrinsics. +func (mr *MockInstanceMockRecorder) InherentExtrinsics(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InherentExtrinsics", reflect.TypeOf((*MockInstance)(nil).InherentExtrinsics), arg0) +} + +// InitializeBlock mocks base method. +func (m *MockInstance) InitializeBlock(arg0 *types.Header) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InitializeBlock", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// InitializeBlock indicates an expected call of InitializeBlock. +func (mr *MockInstanceMockRecorder) InitializeBlock(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitializeBlock", reflect.TypeOf((*MockInstance)(nil).InitializeBlock), arg0) +} + +// Keystore mocks base method. +func (m *MockInstance) Keystore() *keystore.GlobalKeystore { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Keystore") + ret0, _ := ret[0].(*keystore.GlobalKeystore) + return ret0 +} + +// Keystore indicates an expected call of Keystore. +func (mr *MockInstanceMockRecorder) Keystore() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Keystore", reflect.TypeOf((*MockInstance)(nil).Keystore)) +} + +// Metadata mocks base method. +func (m *MockInstance) Metadata() ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Metadata") + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Metadata indicates an expected call of Metadata. +func (mr *MockInstanceMockRecorder) Metadata() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Metadata", reflect.TypeOf((*MockInstance)(nil).Metadata)) +} + +// NetworkService mocks base method. +func (m *MockInstance) NetworkService() runtime.BasicNetwork { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetworkService") + ret0, _ := ret[0].(runtime.BasicNetwork) + return ret0 +} + +// NetworkService indicates an expected call of NetworkService. +func (mr *MockInstanceMockRecorder) NetworkService() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetworkService", reflect.TypeOf((*MockInstance)(nil).NetworkService)) +} + +// NodeStorage mocks base method. +func (m *MockInstance) NodeStorage() runtime.NodeStorage { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NodeStorage") + ret0, _ := ret[0].(runtime.NodeStorage) + return ret0 +} + +// NodeStorage indicates an expected call of NodeStorage. +func (mr *MockInstanceMockRecorder) NodeStorage() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NodeStorage", reflect.TypeOf((*MockInstance)(nil).NodeStorage)) +} + +// OffchainWorker mocks base method. +func (m *MockInstance) OffchainWorker() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "OffchainWorker") +} + +// OffchainWorker indicates an expected call of OffchainWorker. +func (mr *MockInstanceMockRecorder) OffchainWorker() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OffchainWorker", reflect.TypeOf((*MockInstance)(nil).OffchainWorker)) +} + +// PaymentQueryInfo mocks base method. +func (m *MockInstance) PaymentQueryInfo(arg0 []byte) (*types.TransactionPaymentQueryInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaymentQueryInfo", arg0) + ret0, _ := ret[0].(*types.TransactionPaymentQueryInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaymentQueryInfo indicates an expected call of PaymentQueryInfo. +func (mr *MockInstanceMockRecorder) PaymentQueryInfo(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaymentQueryInfo", reflect.TypeOf((*MockInstance)(nil).PaymentQueryInfo), arg0) +} + +// RandomSeed mocks base method. +func (m *MockInstance) RandomSeed() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RandomSeed") +} + +// RandomSeed indicates an expected call of RandomSeed. +func (mr *MockInstanceMockRecorder) RandomSeed() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RandomSeed", reflect.TypeOf((*MockInstance)(nil).RandomSeed)) +} + +// SetContextStorage mocks base method. +func (m *MockInstance) SetContextStorage(arg0 runtime.Storage) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetContextStorage", arg0) +} + +// SetContextStorage indicates an expected call of SetContextStorage. +func (mr *MockInstanceMockRecorder) SetContextStorage(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetContextStorage", reflect.TypeOf((*MockInstance)(nil).SetContextStorage), arg0) +} + +// Stop mocks base method. +func (m *MockInstance) Stop() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Stop") +} + +// Stop indicates an expected call of Stop. +func (mr *MockInstanceMockRecorder) Stop() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stop", reflect.TypeOf((*MockInstance)(nil).Stop)) +} + +// UpdateRuntimeCode mocks base method. +func (m *MockInstance) UpdateRuntimeCode(arg0 []byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateRuntimeCode", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateRuntimeCode indicates an expected call of UpdateRuntimeCode. +func (mr *MockInstanceMockRecorder) UpdateRuntimeCode(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateRuntimeCode", reflect.TypeOf((*MockInstance)(nil).UpdateRuntimeCode), arg0) +} + +// ValidateTransaction mocks base method. +func (m *MockInstance) ValidateTransaction(arg0 types.Extrinsic) (*transaction.Validity, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ValidateTransaction", arg0) + ret0, _ := ret[0].(*transaction.Validity) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ValidateTransaction indicates an expected call of ValidateTransaction. +func (mr *MockInstanceMockRecorder) ValidateTransaction(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateTransaction", reflect.TypeOf((*MockInstance)(nil).ValidateTransaction), arg0) +} + +// Validator mocks base method. +func (m *MockInstance) Validator() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Validator") + ret0, _ := ret[0].(bool) + return ret0 +} + +// Validator indicates an expected call of Validator. +func (mr *MockInstanceMockRecorder) Validator() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validator", reflect.TypeOf((*MockInstance)(nil).Validator)) +} + +// Version mocks base method. +func (m *MockInstance) Version() (runtime.Version, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Version") + ret0, _ := ret[0].(runtime.Version) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Version indicates an expected call of Version. +func (mr *MockInstanceMockRecorder) Version() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Version", reflect.TypeOf((*MockInstance)(nil).Version)) +} diff --git a/dot/sync/mock_interface_test.go b/dot/sync/mock_interface_test.go new file mode 100644 index 0000000000..c34f41db3e --- /dev/null +++ b/dot/sync/mock_interface_test.go @@ -0,0 +1,773 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ChainSafe/gossamer/dot/sync (interfaces: BlockState,StorageState,CodeSubstitutedState,TransactionState,BabeVerifier,FinalityGadget,BlockImportHandler,Network) + +// Package sync is a generated GoMock package. +package sync + +import ( + reflect "reflect" + + network "github.com/ChainSafe/gossamer/dot/network" + peerset "github.com/ChainSafe/gossamer/dot/peerset" + types "github.com/ChainSafe/gossamer/dot/types" + common "github.com/ChainSafe/gossamer/lib/common" + runtime "github.com/ChainSafe/gossamer/lib/runtime" + storage "github.com/ChainSafe/gossamer/lib/runtime/storage" + gomock "github.com/golang/mock/gomock" + peer "github.com/libp2p/go-libp2p-core/peer" +) + +// MockBlockState is a mock of BlockState interface. +type MockBlockState struct { + ctrl *gomock.Controller + recorder *MockBlockStateMockRecorder +} + +// MockBlockStateMockRecorder is the mock recorder for MockBlockState. +type MockBlockStateMockRecorder struct { + mock *MockBlockState +} + +// NewMockBlockState creates a new mock instance. +func NewMockBlockState(ctrl *gomock.Controller) *MockBlockState { + mock := &MockBlockState{ctrl: ctrl} + mock.recorder = &MockBlockStateMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockBlockState) EXPECT() *MockBlockStateMockRecorder { + return m.recorder +} + +// AddBlock mocks base method. +func (m *MockBlockState) AddBlock(arg0 *types.Block) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddBlock", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddBlock indicates an expected call of AddBlock. +func (mr *MockBlockStateMockRecorder) AddBlock(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddBlock", reflect.TypeOf((*MockBlockState)(nil).AddBlock), arg0) +} + +// AddBlockToBlockTree mocks base method. +func (m *MockBlockState) AddBlockToBlockTree(arg0 *types.Block) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddBlockToBlockTree", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddBlockToBlockTree indicates an expected call of AddBlockToBlockTree. +func (mr *MockBlockStateMockRecorder) AddBlockToBlockTree(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddBlockToBlockTree", reflect.TypeOf((*MockBlockState)(nil).AddBlockToBlockTree), arg0) +} + +// BestBlockHash mocks base method. +func (m *MockBlockState) BestBlockHash() common.Hash { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BestBlockHash") + ret0, _ := ret[0].(common.Hash) + return ret0 +} + +// BestBlockHash indicates an expected call of BestBlockHash. +func (mr *MockBlockStateMockRecorder) BestBlockHash() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BestBlockHash", reflect.TypeOf((*MockBlockState)(nil).BestBlockHash)) +} + +// BestBlockHeader mocks base method. +func (m *MockBlockState) BestBlockHeader() (*types.Header, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BestBlockHeader") + ret0, _ := ret[0].(*types.Header) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BestBlockHeader indicates an expected call of BestBlockHeader. +func (mr *MockBlockStateMockRecorder) BestBlockHeader() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BestBlockHeader", reflect.TypeOf((*MockBlockState)(nil).BestBlockHeader)) +} + +// BestBlockNumber mocks base method. +func (m *MockBlockState) BestBlockNumber() (uint, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BestBlockNumber") + ret0, _ := ret[0].(uint) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BestBlockNumber indicates an expected call of BestBlockNumber. +func (mr *MockBlockStateMockRecorder) BestBlockNumber() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BestBlockNumber", reflect.TypeOf((*MockBlockState)(nil).BestBlockNumber)) +} + +// CompareAndSetBlockData mocks base method. +func (m *MockBlockState) CompareAndSetBlockData(arg0 *types.BlockData) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CompareAndSetBlockData", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// CompareAndSetBlockData indicates an expected call of CompareAndSetBlockData. +func (mr *MockBlockStateMockRecorder) CompareAndSetBlockData(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CompareAndSetBlockData", reflect.TypeOf((*MockBlockState)(nil).CompareAndSetBlockData), arg0) +} + +// GetAllBlocksAtNumber mocks base method. +func (m *MockBlockState) GetAllBlocksAtNumber(arg0 uint) ([]common.Hash, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAllBlocksAtNumber", arg0) + ret0, _ := ret[0].([]common.Hash) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAllBlocksAtNumber indicates an expected call of GetAllBlocksAtNumber. +func (mr *MockBlockStateMockRecorder) GetAllBlocksAtNumber(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllBlocksAtNumber", reflect.TypeOf((*MockBlockState)(nil).GetAllBlocksAtNumber), arg0) +} + +// GetBlockBody mocks base method. +func (m *MockBlockState) GetBlockBody(arg0 common.Hash) (*types.Body, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBlockBody", arg0) + ret0, _ := ret[0].(*types.Body) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBlockBody indicates an expected call of GetBlockBody. +func (mr *MockBlockStateMockRecorder) GetBlockBody(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlockBody", reflect.TypeOf((*MockBlockState)(nil).GetBlockBody), arg0) +} + +// GetBlockByHash mocks base method. +func (m *MockBlockState) GetBlockByHash(arg0 common.Hash) (*types.Block, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBlockByHash", arg0) + ret0, _ := ret[0].(*types.Block) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBlockByHash indicates an expected call of GetBlockByHash. +func (mr *MockBlockStateMockRecorder) GetBlockByHash(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlockByHash", reflect.TypeOf((*MockBlockState)(nil).GetBlockByHash), arg0) +} + +// GetBlockByNumber mocks base method. +func (m *MockBlockState) GetBlockByNumber(arg0 uint) (*types.Block, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBlockByNumber", arg0) + ret0, _ := ret[0].(*types.Block) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBlockByNumber indicates an expected call of GetBlockByNumber. +func (mr *MockBlockStateMockRecorder) GetBlockByNumber(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlockByNumber", reflect.TypeOf((*MockBlockState)(nil).GetBlockByNumber), arg0) +} + +// GetFinalisedNotifierChannel mocks base method. +func (m *MockBlockState) GetFinalisedNotifierChannel() chan *types.FinalisationInfo { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFinalisedNotifierChannel") + ret0, _ := ret[0].(chan *types.FinalisationInfo) + return ret0 +} + +// GetFinalisedNotifierChannel indicates an expected call of GetFinalisedNotifierChannel. +func (mr *MockBlockStateMockRecorder) GetFinalisedNotifierChannel() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFinalisedNotifierChannel", reflect.TypeOf((*MockBlockState)(nil).GetFinalisedNotifierChannel)) +} + +// GetHashByNumber mocks base method. +func (m *MockBlockState) GetHashByNumber(arg0 uint) (common.Hash, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetHashByNumber", arg0) + ret0, _ := ret[0].(common.Hash) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetHashByNumber indicates an expected call of GetHashByNumber. +func (mr *MockBlockStateMockRecorder) GetHashByNumber(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHashByNumber", reflect.TypeOf((*MockBlockState)(nil).GetHashByNumber), arg0) +} + +// GetHeader mocks base method. +func (m *MockBlockState) GetHeader(arg0 common.Hash) (*types.Header, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetHeader", arg0) + ret0, _ := ret[0].(*types.Header) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetHeader indicates an expected call of GetHeader. +func (mr *MockBlockStateMockRecorder) GetHeader(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHeader", reflect.TypeOf((*MockBlockState)(nil).GetHeader), arg0) +} + +// GetHeaderByNumber mocks base method. +func (m *MockBlockState) GetHeaderByNumber(arg0 uint) (*types.Header, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetHeaderByNumber", arg0) + ret0, _ := ret[0].(*types.Header) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetHeaderByNumber indicates an expected call of GetHeaderByNumber. +func (mr *MockBlockStateMockRecorder) GetHeaderByNumber(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHeaderByNumber", reflect.TypeOf((*MockBlockState)(nil).GetHeaderByNumber), arg0) +} + +// GetHighestFinalisedHeader mocks base method. +func (m *MockBlockState) GetHighestFinalisedHeader() (*types.Header, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetHighestFinalisedHeader") + ret0, _ := ret[0].(*types.Header) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetHighestFinalisedHeader indicates an expected call of GetHighestFinalisedHeader. +func (mr *MockBlockStateMockRecorder) GetHighestFinalisedHeader() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHighestFinalisedHeader", reflect.TypeOf((*MockBlockState)(nil).GetHighestFinalisedHeader)) +} + +// GetJustification mocks base method. +func (m *MockBlockState) GetJustification(arg0 common.Hash) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetJustification", arg0) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetJustification indicates an expected call of GetJustification. +func (mr *MockBlockStateMockRecorder) GetJustification(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetJustification", reflect.TypeOf((*MockBlockState)(nil).GetJustification), arg0) +} + +// GetMessageQueue mocks base method. +func (m *MockBlockState) GetMessageQueue(arg0 common.Hash) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMessageQueue", arg0) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMessageQueue indicates an expected call of GetMessageQueue. +func (mr *MockBlockStateMockRecorder) GetMessageQueue(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessageQueue", reflect.TypeOf((*MockBlockState)(nil).GetMessageQueue), arg0) +} + +// GetReceipt mocks base method. +func (m *MockBlockState) GetReceipt(arg0 common.Hash) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetReceipt", arg0) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetReceipt indicates an expected call of GetReceipt. +func (mr *MockBlockStateMockRecorder) GetReceipt(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetReceipt", reflect.TypeOf((*MockBlockState)(nil).GetReceipt), arg0) +} + +// GetRuntime mocks base method. +func (m *MockBlockState) GetRuntime(arg0 *common.Hash) (runtime.Instance, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRuntime", arg0) + ret0, _ := ret[0].(runtime.Instance) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRuntime indicates an expected call of GetRuntime. +func (mr *MockBlockStateMockRecorder) GetRuntime(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRuntime", reflect.TypeOf((*MockBlockState)(nil).GetRuntime), arg0) +} + +// HasBlockBody mocks base method. +func (m *MockBlockState) HasBlockBody(arg0 common.Hash) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasBlockBody", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HasBlockBody indicates an expected call of HasBlockBody. +func (mr *MockBlockStateMockRecorder) HasBlockBody(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasBlockBody", reflect.TypeOf((*MockBlockState)(nil).HasBlockBody), arg0) +} + +// HasHeader mocks base method. +func (m *MockBlockState) HasHeader(arg0 common.Hash) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasHeader", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HasHeader indicates an expected call of HasHeader. +func (mr *MockBlockStateMockRecorder) HasHeader(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasHeader", reflect.TypeOf((*MockBlockState)(nil).HasHeader), arg0) +} + +// IsDescendantOf mocks base method. +func (m *MockBlockState) IsDescendantOf(arg0, arg1 common.Hash) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsDescendantOf", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// IsDescendantOf indicates an expected call of IsDescendantOf. +func (mr *MockBlockStateMockRecorder) IsDescendantOf(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsDescendantOf", reflect.TypeOf((*MockBlockState)(nil).IsDescendantOf), arg0, arg1) +} + +// SetFinalisedHash mocks base method. +func (m *MockBlockState) SetFinalisedHash(arg0 common.Hash, arg1, arg2 uint64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetFinalisedHash", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetFinalisedHash indicates an expected call of SetFinalisedHash. +func (mr *MockBlockStateMockRecorder) SetFinalisedHash(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetFinalisedHash", reflect.TypeOf((*MockBlockState)(nil).SetFinalisedHash), arg0, arg1, arg2) +} + +// SetHeader mocks base method. +func (m *MockBlockState) SetHeader(arg0 *types.Header) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetHeader", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetHeader indicates an expected call of SetHeader. +func (mr *MockBlockStateMockRecorder) SetHeader(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetHeader", reflect.TypeOf((*MockBlockState)(nil).SetHeader), arg0) +} + +// SetJustification mocks base method. +func (m *MockBlockState) SetJustification(arg0 common.Hash, arg1 []byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetJustification", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetJustification indicates an expected call of SetJustification. +func (mr *MockBlockStateMockRecorder) SetJustification(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetJustification", reflect.TypeOf((*MockBlockState)(nil).SetJustification), arg0, arg1) +} + +// StoreRuntime mocks base method. +func (m *MockBlockState) StoreRuntime(arg0 common.Hash, arg1 runtime.Instance) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "StoreRuntime", arg0, arg1) +} + +// StoreRuntime indicates an expected call of StoreRuntime. +func (mr *MockBlockStateMockRecorder) StoreRuntime(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StoreRuntime", reflect.TypeOf((*MockBlockState)(nil).StoreRuntime), arg0, arg1) +} + +// SubChain mocks base method. +func (m *MockBlockState) SubChain(arg0, arg1 common.Hash) ([]common.Hash, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubChain", arg0, arg1) + ret0, _ := ret[0].([]common.Hash) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SubChain indicates an expected call of SubChain. +func (mr *MockBlockStateMockRecorder) SubChain(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubChain", reflect.TypeOf((*MockBlockState)(nil).SubChain), arg0, arg1) +} + +// MockStorageState is a mock of StorageState interface. +type MockStorageState struct { + ctrl *gomock.Controller + recorder *MockStorageStateMockRecorder +} + +// MockStorageStateMockRecorder is the mock recorder for MockStorageState. +type MockStorageStateMockRecorder struct { + mock *MockStorageState +} + +// NewMockStorageState creates a new mock instance. +func NewMockStorageState(ctrl *gomock.Controller) *MockStorageState { + mock := &MockStorageState{ctrl: ctrl} + mock.recorder = &MockStorageStateMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStorageState) EXPECT() *MockStorageStateMockRecorder { + return m.recorder +} + +// LoadCodeHash mocks base method. +func (m *MockStorageState) LoadCodeHash(arg0 *common.Hash) (common.Hash, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LoadCodeHash", arg0) + ret0, _ := ret[0].(common.Hash) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LoadCodeHash indicates an expected call of LoadCodeHash. +func (mr *MockStorageStateMockRecorder) LoadCodeHash(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadCodeHash", reflect.TypeOf((*MockStorageState)(nil).LoadCodeHash), arg0) +} + +// Lock mocks base method. +func (m *MockStorageState) Lock() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Lock") +} + +// Lock indicates an expected call of Lock. +func (mr *MockStorageStateMockRecorder) Lock() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Lock", reflect.TypeOf((*MockStorageState)(nil).Lock)) +} + +// TrieState mocks base method. +func (m *MockStorageState) TrieState(arg0 *common.Hash) (*storage.TrieState, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TrieState", arg0) + ret0, _ := ret[0].(*storage.TrieState) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// TrieState indicates an expected call of TrieState. +func (mr *MockStorageStateMockRecorder) TrieState(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TrieState", reflect.TypeOf((*MockStorageState)(nil).TrieState), arg0) +} + +// Unlock mocks base method. +func (m *MockStorageState) Unlock() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Unlock") +} + +// Unlock indicates an expected call of Unlock. +func (mr *MockStorageStateMockRecorder) Unlock() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockStorageState)(nil).Unlock)) +} + +// MockCodeSubstitutedState is a mock of CodeSubstitutedState interface. +type MockCodeSubstitutedState struct { + ctrl *gomock.Controller + recorder *MockCodeSubstitutedStateMockRecorder +} + +// MockCodeSubstitutedStateMockRecorder is the mock recorder for MockCodeSubstitutedState. +type MockCodeSubstitutedStateMockRecorder struct { + mock *MockCodeSubstitutedState +} + +// NewMockCodeSubstitutedState creates a new mock instance. +func NewMockCodeSubstitutedState(ctrl *gomock.Controller) *MockCodeSubstitutedState { + mock := &MockCodeSubstitutedState{ctrl: ctrl} + mock.recorder = &MockCodeSubstitutedStateMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCodeSubstitutedState) EXPECT() *MockCodeSubstitutedStateMockRecorder { + return m.recorder +} + +// LoadCodeSubstitutedBlockHash mocks base method. +func (m *MockCodeSubstitutedState) LoadCodeSubstitutedBlockHash() common.Hash { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LoadCodeSubstitutedBlockHash") + ret0, _ := ret[0].(common.Hash) + return ret0 +} + +// LoadCodeSubstitutedBlockHash indicates an expected call of LoadCodeSubstitutedBlockHash. +func (mr *MockCodeSubstitutedStateMockRecorder) LoadCodeSubstitutedBlockHash() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadCodeSubstitutedBlockHash", reflect.TypeOf((*MockCodeSubstitutedState)(nil).LoadCodeSubstitutedBlockHash)) +} + +// StoreCodeSubstitutedBlockHash mocks base method. +func (m *MockCodeSubstitutedState) StoreCodeSubstitutedBlockHash(arg0 common.Hash) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StoreCodeSubstitutedBlockHash", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// StoreCodeSubstitutedBlockHash indicates an expected call of StoreCodeSubstitutedBlockHash. +func (mr *MockCodeSubstitutedStateMockRecorder) StoreCodeSubstitutedBlockHash(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StoreCodeSubstitutedBlockHash", reflect.TypeOf((*MockCodeSubstitutedState)(nil).StoreCodeSubstitutedBlockHash), arg0) +} + +// MockTransactionState is a mock of TransactionState interface. +type MockTransactionState struct { + ctrl *gomock.Controller + recorder *MockTransactionStateMockRecorder +} + +// MockTransactionStateMockRecorder is the mock recorder for MockTransactionState. +type MockTransactionStateMockRecorder struct { + mock *MockTransactionState +} + +// NewMockTransactionState creates a new mock instance. +func NewMockTransactionState(ctrl *gomock.Controller) *MockTransactionState { + mock := &MockTransactionState{ctrl: ctrl} + mock.recorder = &MockTransactionStateMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockTransactionState) EXPECT() *MockTransactionStateMockRecorder { + return m.recorder +} + +// RemoveExtrinsic mocks base method. +func (m *MockTransactionState) RemoveExtrinsic(arg0 types.Extrinsic) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RemoveExtrinsic", arg0) +} + +// RemoveExtrinsic indicates an expected call of RemoveExtrinsic. +func (mr *MockTransactionStateMockRecorder) RemoveExtrinsic(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveExtrinsic", reflect.TypeOf((*MockTransactionState)(nil).RemoveExtrinsic), arg0) +} + +// MockBabeVerifier is a mock of BabeVerifier interface. +type MockBabeVerifier struct { + ctrl *gomock.Controller + recorder *MockBabeVerifierMockRecorder +} + +// MockBabeVerifierMockRecorder is the mock recorder for MockBabeVerifier. +type MockBabeVerifierMockRecorder struct { + mock *MockBabeVerifier +} + +// NewMockBabeVerifier creates a new mock instance. +func NewMockBabeVerifier(ctrl *gomock.Controller) *MockBabeVerifier { + mock := &MockBabeVerifier{ctrl: ctrl} + mock.recorder = &MockBabeVerifierMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockBabeVerifier) EXPECT() *MockBabeVerifierMockRecorder { + return m.recorder +} + +// VerifyBlock mocks base method. +func (m *MockBabeVerifier) VerifyBlock(arg0 *types.Header) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VerifyBlock", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// VerifyBlock indicates an expected call of VerifyBlock. +func (mr *MockBabeVerifierMockRecorder) VerifyBlock(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyBlock", reflect.TypeOf((*MockBabeVerifier)(nil).VerifyBlock), arg0) +} + +// MockFinalityGadget is a mock of FinalityGadget interface. +type MockFinalityGadget struct { + ctrl *gomock.Controller + recorder *MockFinalityGadgetMockRecorder +} + +// MockFinalityGadgetMockRecorder is the mock recorder for MockFinalityGadget. +type MockFinalityGadgetMockRecorder struct { + mock *MockFinalityGadget +} + +// NewMockFinalityGadget creates a new mock instance. +func NewMockFinalityGadget(ctrl *gomock.Controller) *MockFinalityGadget { + mock := &MockFinalityGadget{ctrl: ctrl} + mock.recorder = &MockFinalityGadgetMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockFinalityGadget) EXPECT() *MockFinalityGadgetMockRecorder { + return m.recorder +} + +// VerifyBlockJustification mocks base method. +func (m *MockFinalityGadget) VerifyBlockJustification(arg0 common.Hash, arg1 []byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VerifyBlockJustification", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// VerifyBlockJustification indicates an expected call of VerifyBlockJustification. +func (mr *MockFinalityGadgetMockRecorder) VerifyBlockJustification(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyBlockJustification", reflect.TypeOf((*MockFinalityGadget)(nil).VerifyBlockJustification), arg0, arg1) +} + +// MockBlockImportHandler is a mock of BlockImportHandler interface. +type MockBlockImportHandler struct { + ctrl *gomock.Controller + recorder *MockBlockImportHandlerMockRecorder +} + +// MockBlockImportHandlerMockRecorder is the mock recorder for MockBlockImportHandler. +type MockBlockImportHandlerMockRecorder struct { + mock *MockBlockImportHandler +} + +// NewMockBlockImportHandler creates a new mock instance. +func NewMockBlockImportHandler(ctrl *gomock.Controller) *MockBlockImportHandler { + mock := &MockBlockImportHandler{ctrl: ctrl} + mock.recorder = &MockBlockImportHandlerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockBlockImportHandler) EXPECT() *MockBlockImportHandlerMockRecorder { + return m.recorder +} + +// HandleBlockImport mocks base method. +func (m *MockBlockImportHandler) HandleBlockImport(arg0 *types.Block, arg1 *storage.TrieState) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HandleBlockImport", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// HandleBlockImport indicates an expected call of HandleBlockImport. +func (mr *MockBlockImportHandlerMockRecorder) HandleBlockImport(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleBlockImport", reflect.TypeOf((*MockBlockImportHandler)(nil).HandleBlockImport), arg0, arg1) +} + +// MockNetwork is a mock of Network interface. +type MockNetwork struct { + ctrl *gomock.Controller + recorder *MockNetworkMockRecorder +} + +// MockNetworkMockRecorder is the mock recorder for MockNetwork. +type MockNetworkMockRecorder struct { + mock *MockNetwork +} + +// NewMockNetwork creates a new mock instance. +func NewMockNetwork(ctrl *gomock.Controller) *MockNetwork { + mock := &MockNetwork{ctrl: ctrl} + mock.recorder = &MockNetworkMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockNetwork) EXPECT() *MockNetworkMockRecorder { + return m.recorder +} + +// DoBlockRequest mocks base method. +func (m *MockNetwork) DoBlockRequest(arg0 peer.ID, arg1 *network.BlockRequestMessage) (*network.BlockResponseMessage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DoBlockRequest", arg0, arg1) + ret0, _ := ret[0].(*network.BlockResponseMessage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DoBlockRequest indicates an expected call of DoBlockRequest. +func (mr *MockNetworkMockRecorder) DoBlockRequest(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DoBlockRequest", reflect.TypeOf((*MockNetwork)(nil).DoBlockRequest), arg0, arg1) +} + +// Peers mocks base method. +func (m *MockNetwork) Peers() []common.PeerInfo { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Peers") + ret0, _ := ret[0].([]common.PeerInfo) + return ret0 +} + +// Peers indicates an expected call of Peers. +func (mr *MockNetworkMockRecorder) Peers() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Peers", reflect.TypeOf((*MockNetwork)(nil).Peers)) +} + +// ReportPeer mocks base method. +func (m *MockNetwork) ReportPeer(arg0 peerset.ReputationChange, arg1 peer.ID) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "ReportPeer", arg0, arg1) +} + +// ReportPeer indicates an expected call of ReportPeer. +func (mr *MockNetworkMockRecorder) ReportPeer(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportPeer", reflect.TypeOf((*MockNetwork)(nil).ReportPeer), arg0, arg1) +} diff --git a/dot/sync/outliers.go b/dot/sync/outliers.go index 3e1c29424f..be33e69a1a 100644 --- a/dot/sync/outliers.go +++ b/dot/sync/outliers.go @@ -8,7 +8,7 @@ import ( "sort" ) -// removeOutliers removes the outlier from the slice +// nonOutliersSumCount calculates the sum and count of non-outlier elements // Explanation: // IQR outlier detection // Q25 = 25th_percentile @@ -18,8 +18,8 @@ import ( // If x > Q75 + 3.0 * IQR or x < Q25 – 3.0 * IQR THEN x is a extreme outlier // Ref: http://www.mathwords.com/o/outlier.htm // -// returns: sum of all the non-outliers elements -func removeOutliers(dataArrUint []uint) (sum *big.Int, count uint) { +// returns: sum and count of all the non-outliers elements +func nonOutliersSumCount(dataArrUint []uint) (sum *big.Int, count uint) { dataArr := make([]*big.Int, len(dataArrUint)) for i, v := range dataArrUint { dataArr[i] = big.NewInt(int64(v)) diff --git a/dot/sync/outliers_integeration_test.go b/dot/sync/outliers_integeration_test.go deleted file mode 100644 index 86d5313094..0000000000 --- a/dot/sync/outliers_integeration_test.go +++ /dev/null @@ -1,27 +0,0 @@ -//go:build integration -// +build integration - -// Copyright 2021 ChainSafe Systems (ON) -// SPDX-License-Identifier: LGPL-3.0-only - -package sync - -import ( - "math/big" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestRemoveOutlier(t *testing.T) { - t.Parallel() - - arr := []uint{100, 0, 260, 280, 220, 240, 250, 1000} - - expectedSum := big.NewInt(1350) // excluding the outlier -100 and 1000 - expectedCount := uint(7) - - sum, count := removeOutliers(arr) - assert.Equal(t, expectedSum, sum) - assert.Equal(t, expectedCount, count) -} diff --git a/dot/sync/outliers_test.go b/dot/sync/outliers_test.go new file mode 100644 index 0000000000..a407d654d9 --- /dev/null +++ b/dot/sync/outliers_test.go @@ -0,0 +1,46 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package sync + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_nonOutliersSumCount(t *testing.T) { + tests := []struct { + name string + dataArr []uint + wantSum *big.Int + wantCount uint + }{ + { + name: "case 0 outliers", + dataArr: []uint{2, 5, 6, 9, 12}, + wantSum: big.NewInt(34), + wantCount: uint(5), + }, + { + name: "case 1 outliers", + dataArr: []uint{100, 2, 260, 280, 220, 240, 250, 1000}, + wantSum: big.NewInt(1352), + wantCount: uint(7), + }, + { + name: "case 2 outliers", + dataArr: []uint{5000, 500, 5560, 5580, 5520, 5540, 5550, 100000}, + wantSum: big.NewInt(32750), + wantCount: uint(6), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotSum, gotCount := nonOutliersSumCount(tt.dataArr) + assert.Equal(t, tt.wantSum, gotSum) + assert.Equal(t, tt.wantCount, gotCount) + }) + } +} diff --git a/dot/sync/syncer.go b/dot/sync/syncer.go index a9c5921a7a..6cf7965ada 100644 --- a/dot/sync/syncer.go +++ b/dot/sync/syncer.go @@ -100,7 +100,7 @@ func NewService(cfg *Config) (*Service, error) { // Start begins the chainSync and chainProcessor modules. It begins syncing in bootstrap mode func (s *Service) Start() error { go s.chainSync.start() - go s.chainProcessor.start() + s.chainProcessor.start() return nil } diff --git a/dot/sync/syncer_integeration_test.go b/dot/sync/syncer_integeration_test.go index e495534d11..fd1f21a012 100644 --- a/dot/sync/syncer_integeration_test.go +++ b/dot/sync/syncer_integeration_test.go @@ -27,19 +27,6 @@ import ( "github.com/stretchr/testify/require" ) -func newMockFinalityGadget() *mocks.FinalityGadget { - m := new(mocks.FinalityGadget) - // using []uint8 instead of []byte: https://github.com/stretchr/testify/pull/969 - m.On("VerifyBlockJustification", mock.AnythingOfType("common.Hash"), mock.AnythingOfType("[]uint8")).Return(nil) - return m -} - -func newMockBabeVerifier() *mocks.BabeVerifier { - m := new(mocks.BabeVerifier) - m.On("VerifyBlock", mock.AnythingOfType("*types.Header")).Return(nil) - return m -} - func newMockNetwork() *mocks.Network { m := new(mocks.Network) m.On("DoBlockRequest", mock.AnythingOfType("peer.ID"), @@ -127,12 +114,16 @@ func newTestSyncer(t *testing.T) *Service { }) cfg.TransactionState = stateSrvc.Transaction - cfg.BabeVerifier = newMockBabeVerifier() + mockBabeVerifier := NewMockBabeVerifier(ctrl) + mockBabeVerifier.EXPECT().VerifyBlock(gomock.AssignableToTypeOf(&types.Header{})).AnyTimes() + cfg.BabeVerifier = mockBabeVerifier cfg.LogLvl = log.Trace - cfg.FinalityGadget = newMockFinalityGadget() + mockFinalityGadget := NewMockFinalityGadget(ctrl) + mockFinalityGadget.EXPECT().VerifyBlockJustification(gomock.AssignableToTypeOf(common.Hash{}), + gomock.AssignableToTypeOf([]byte{})).AnyTimes() + cfg.FinalityGadget = mockFinalityGadget cfg.Network = newMockNetwork() cfg.Telemetry = mockTelemetryClient - syncer, err := NewService(cfg) require.NoError(t, err) return syncer diff --git a/dot/sync/syncer_test.go b/dot/sync/syncer_test.go new file mode 100644 index 0000000000..fb45ee57df --- /dev/null +++ b/dot/sync/syncer_test.go @@ -0,0 +1,409 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package sync + +import ( + "errors" + "testing" + + "github.com/ChainSafe/gossamer/dot/network" + "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/pkg/scale" + "github.com/golang/mock/gomock" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/stretchr/testify/assert" +) + +func TestNewService(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + cfgBuilder func(ctrl *gomock.Controller) *Config + want *Service + err error + }{ + { + name: "nil Network", + cfgBuilder: func(_ *gomock.Controller) *Config { + return &Config{} + }, + err: errNilNetwork, + }, + { + name: "nil BlockState", + cfgBuilder: func(_ *gomock.Controller) *Config { + return &Config{ + Network: NewMockNetwork(nil), + } + }, + err: errNilBlockState, + }, + { + name: "nil StorageState", + cfgBuilder: func(_ *gomock.Controller) *Config { + return &Config{ + Network: NewMockNetwork(nil), + BlockState: NewMockBlockState(nil), + } + }, + err: errNilStorageState, + }, + { + name: "nil FinalityGadget", + cfgBuilder: func(_ *gomock.Controller) *Config { + return &Config{ + Network: NewMockNetwork(nil), + BlockState: NewMockBlockState(nil), + StorageState: NewMockStorageState(nil), + } + }, + err: errNilFinalityGadget, + }, + { + name: "nil TransactionState", + cfgBuilder: func(_ *gomock.Controller) *Config { + return &Config{ + Network: NewMockNetwork(nil), + BlockState: NewMockBlockState(nil), + StorageState: NewMockStorageState(nil), + FinalityGadget: NewMockFinalityGadget(nil), + } + }, + err: errNilTransactionState, + }, + { + name: "nil Verifier", + cfgBuilder: func(_ *gomock.Controller) *Config { + return &Config{ + Network: NewMockNetwork(nil), + BlockState: NewMockBlockState(nil), + StorageState: NewMockStorageState(nil), + FinalityGadget: NewMockFinalityGadget(nil), + TransactionState: NewMockTransactionState(nil), + } + }, + err: errNilVerifier, + }, + { + name: "nil BlockImportHandler", + cfgBuilder: func(_ *gomock.Controller) *Config { + return &Config{ + Network: NewMockNetwork(nil), + BlockState: NewMockBlockState(nil), + StorageState: NewMockStorageState(nil), + FinalityGadget: NewMockFinalityGadget(nil), + TransactionState: NewMockTransactionState(nil), + BabeVerifier: NewMockBabeVerifier(nil), + } + }, + err: errNilBlockImportHandler, + }, + { + name: "working example", + cfgBuilder: func(ctrl *gomock.Controller) *Config { + blockState := NewMockBlockState(ctrl) + blockState.EXPECT().GetFinalisedNotifierChannel(). + Return(make(chan *types.FinalisationInfo)) + return &Config{ + Network: NewMockNetwork(nil), + BlockState: blockState, + StorageState: NewMockStorageState(nil), + FinalityGadget: NewMockFinalityGadget(nil), + TransactionState: NewMockTransactionState(nil), + BabeVerifier: NewMockBabeVerifier(nil), + BlockImportHandler: NewMockBlockImportHandler(nil), + } + }, + want: &Service{}, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + + config := tt.cfgBuilder(ctrl) + + got, err := NewService(config) + if tt.err != nil { + assert.EqualError(t, err, tt.err.Error()) + } else { + assert.NoError(t, err) + } + if tt.want != nil { + assert.NotNil(t, got) + } + }) + } +} + +func TestService_HandleBlockAnnounce(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + + type fields struct { + chainSync ChainSync + } + type args struct { + from peer.ID + msg *network.BlockAnnounceMessage + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "working example", + fields: fields{ + chainSync: newMockChainSync(ctrl), + }, + args: args{ + from: peer.ID("1"), + msg: &network.BlockAnnounceMessage{ + ParentHash: common.Hash{}, + Number: 1, + StateRoot: common.Hash{}, + ExtrinsicsRoot: common.Hash{}, + Digest: scale.VaryingDataTypeSlice{}, + BestBlock: false, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + s := &Service{ + chainSync: tt.fields.chainSync, + } + if err := s.HandleBlockAnnounce(tt.args.from, tt.args.msg); (err != nil) != tt.wantErr { + t.Errorf("HandleBlockAnnounce() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func newMockChainSync(ctrl *gomock.Controller) ChainSync { + mock := NewMockChainSync(ctrl) + header, _ := types.NewHeader(common.Hash{}, common.Hash{}, common.Hash{}, 1, + scale.VaryingDataTypeSlice{}) + + mock.EXPECT().setBlockAnnounce(peer.ID("1"), header).Return(nil).AnyTimes() + mock.EXPECT().setPeerHead(peer.ID("1"), common.Hash{}, uint(0)).Return(nil).AnyTimes() + mock.EXPECT().syncState().Return(bootstrap).AnyTimes() + mock.EXPECT().start().AnyTimes() + mock.EXPECT().stop().AnyTimes() + mock.EXPECT().getHighestBlock().Return(uint(2), nil).AnyTimes() + + return mock +} + +func Test_Service_HandleBlockAnnounceHandshake(t *testing.T) { + t.Parallel() + + errTest := errors.New("test error") + + testCases := map[string]struct { + serviceBuilder func(ctrl *gomock.Controller) Service + from peer.ID + message *network.BlockAnnounceHandshake + errWrapped error + errMessage string + }{ + "success": { + serviceBuilder: func(ctrl *gomock.Controller) Service { + chainSync := NewMockChainSync(ctrl) + chainSync.EXPECT().setPeerHead(peer.ID("abc"), common.Hash{1}, uint(2)). + Return(nil) + return Service{ + chainSync: chainSync, + } + }, + from: peer.ID("abc"), + message: &network.BlockAnnounceHandshake{ + BestBlockHash: common.Hash{1}, + BestBlockNumber: 2, + }, + }, + "failure": { + serviceBuilder: func(ctrl *gomock.Controller) Service { + chainSync := NewMockChainSync(ctrl) + chainSync.EXPECT().setPeerHead(peer.ID("abc"), common.Hash{1}, uint(2)). + Return(errTest) + return Service{ + chainSync: chainSync, + } + }, + from: peer.ID("abc"), + message: &network.BlockAnnounceHandshake{ + BestBlockHash: common.Hash{1}, + BestBlockNumber: 2, + }, + errWrapped: errTest, + errMessage: "test error", + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + + service := testCase.serviceBuilder(ctrl) + + err := service.HandleBlockAnnounceHandshake(testCase.from, testCase.message) + + assert.ErrorIs(t, err, testCase.errWrapped) + if testCase.errWrapped != nil { + assert.EqualError(t, err, testCase.errMessage) + } + }) + } +} + +func TestService_IsSynced(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + serviceBuilder func(ctrl *gomock.Controller) Service + synced bool + }{ + "tip": { + serviceBuilder: func(ctrl *gomock.Controller) Service { + chainSync := NewMockChainSync(ctrl) + chainSync.EXPECT().syncState().Return(tip) + return Service{ + chainSync: chainSync, + } + }, + synced: true, + }, + "not tip": { + serviceBuilder: func(ctrl *gomock.Controller) Service { + chainSync := NewMockChainSync(ctrl) + chainSync.EXPECT().syncState().Return(bootstrap) + return Service{ + chainSync: chainSync, + } + }, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + + service := testCase.serviceBuilder(ctrl) + + synced := service.IsSynced() + + assert.Equal(t, testCase.synced, synced) + }) + } +} + +func TestService_Start(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + done := make(chan struct{}) + + chainSync := NewMockChainSync(ctrl) + chainSync.EXPECT().start().DoAndReturn(func() { + close(done) + }) + + chainProcessor := NewMockChainProcessor(ctrl) + chainProcessor.EXPECT().start() + + service := Service{ + chainSync: chainSync, + chainProcessor: chainProcessor, + } + + err := service.Start() + <-done + assert.NoError(t, err) +} + +func TestService_Stop(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + + chainSync := NewMockChainSync(ctrl) + chainSync.EXPECT().stop() + chainProcessor := NewMockChainProcessor(ctrl) + chainProcessor.EXPECT().stop() + + service := &Service{ + chainSync: chainSync, + chainProcessor: chainProcessor, + } + + err := service.Stop() + assert.NoError(t, err) +} + +func Test_reverseBlockData(t *testing.T) { + t.Parallel() + + type args struct { + data []*types.BlockData + } + tests := []struct { + name string + args args + expected args + }{ + { + name: "working example", + args: args{data: []*types.BlockData{ + { + Hash: common.MustHexToHash("0x01"), + }, + { + Hash: common.MustHexToHash("0x02"), + }}}, + expected: args{data: []*types.BlockData{{ + Hash: common.MustHexToHash("0x02"), + }, { + Hash: common.MustHexToHash("0x01"), + }}, + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + reverseBlockData(tt.args.data) + assert.Equal(t, tt.expected.data, tt.args.data) + }) + } +} + +func TestService_HighestBlock(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + + chainSync := NewMockChainSync(ctrl) + chainSync.EXPECT().getHighestBlock().Return(uint(2), nil) + + service := &Service{ + chainSync: chainSync, + } + highestBlock := service.HighestBlock() + const expected = uint(2) + assert.Equal(t, expected, highestBlock) +} diff --git a/dot/sync/test_helpers.go b/dot/sync/test_helpers.go index f94beb2c22..5766bd5dec 100644 --- a/dot/sync/test_helpers.go +++ b/dot/sync/test_helpers.go @@ -10,7 +10,6 @@ import ( "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/runtime" "github.com/ChainSafe/gossamer/pkg/scale" - "github.com/stretchr/testify/require" ) diff --git a/dot/sync/tip_syncer_integeration_test.go b/dot/sync/tip_syncer_integeration_test.go index 4014e4c79f..ca32eac06b 100644 --- a/dot/sync/tip_syncer_integeration_test.go +++ b/dot/sync/tip_syncer_integeration_test.go @@ -9,13 +9,14 @@ package sync import ( "context" "testing" + "time" "github.com/ChainSafe/gossamer/dot/network" syncmocks "github.com/ChainSafe/gossamer/dot/sync/mocks" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/trie" - + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -31,12 +32,7 @@ func newTestTipSyncer(t *testing.T) *tipSyncer { readyBlocks := newBlockQueue(maxResponseSize) pendingBlocks := newDisjointBlockSet(pendingBlocksLimit) - cs := &chainSync{ - blockState: bs, - readyBlocks: readyBlocks, - pendingBlocks: pendingBlocks, - } - return newTipSyncer(bs, pendingBlocks, readyBlocks, cs.handleReadyBlock) + return newTipSyncer(bs, pendingBlocks, readyBlocks, nil) } func TestTipSyncer_handleNewPeerState(t *testing.T) { @@ -178,12 +174,18 @@ func TestTipSyncer_handleTick_case1(t *testing.T) { targetNumber: uintPtr(fin.Number), direction: network.Descending, requestData: bootstrapRequestData, - pendingBlock: s.pendingBlocks.getBlock(common.Hash{0xb}), + pendingBlock: &pendingBlock{ + hash: common.Hash{0xb}, + number: 201, + clearAt: time.Unix(0, 0), + }, }, } - w, err = s.handleTick() require.NoError(t, err) + require.NotEmpty(t, w) + assert.Greater(t, w[0].pendingBlock.clearAt, time.Now()) + w[0].pendingBlock.clearAt = time.Unix(0, 0) require.Equal(t, expected, w) require.False(t, s.pendingBlocks.hasBlock(common.Hash{0xa})) require.True(t, s.pendingBlocks.hasBlock(common.Hash{0xb})) @@ -208,18 +210,28 @@ func TestTipSyncer_handleTick_case2(t *testing.T) { targetNumber: uintPtr(header.Number), direction: network.Ascending, requestData: network.RequestedDataBody + network.RequestedDataJustification, - pendingBlock: s.pendingBlocks.getBlock(header.Hash()), + pendingBlock: &pendingBlock{ + hash: header.Hash(), + number: 201, + header: header, + clearAt: time.Time{}, + }, }, } w, err := s.handleTick() require.NoError(t, err) + require.NotEmpty(t, w) + assert.Greater(t, w[0].pendingBlock.clearAt, time.Now()) + w[0].pendingBlock.clearAt = time.Time{} require.Equal(t, expected, w) require.True(t, s.pendingBlocks.hasBlock(header.Hash())) } - func TestTipSyncer_handleTick_case3(t *testing.T) { s := newTestTipSyncer(t) - + s.handleReadyBlock = func(data *types.BlockData) { + s.pendingBlocks.removeBlock(data.Hash) + s.readyBlocks.push(data) + } fin, _ := s.blockState.GetHighestFinalisedHeader() // add pending block w/ full block, HasHeader will return true, so the block will be processed @@ -261,12 +273,21 @@ func TestTipSyncer_handleTick_case3(t *testing.T) { targetNumber: uintPtr(fin.Number), direction: network.Descending, requestData: bootstrapRequestData, - pendingBlock: s.pendingBlocks.getBlock(header.Hash()), + pendingBlock: &pendingBlock{ + hash: header.Hash(), + number: 300, + header: header, + body: &types.Body{}, + clearAt: time.Time{}, + }, }, } w, err = s.handleTick() require.NoError(t, err) + require.NotEmpty(t, w) + assert.Greater(t, w[0].pendingBlock.clearAt, time.Now()) + w[0].pendingBlock.clearAt = time.Time{} require.Equal(t, expected, w) require.True(t, s.pendingBlocks.hasBlock(header.Hash())) @@ -278,7 +299,7 @@ func TestTipSyncer_handleTick_case3(t *testing.T) { require.NoError(t, err) require.Equal(t, []*worker(nil), w) require.False(t, s.pendingBlocks.hasBlock(header.Hash())) - _ = s.readyBlocks.pop(context.Background()) // first pop removes the parent + _ = s.readyBlocks.pop(context.Background()) // first pop will remove parent readyBlockData = s.readyBlocks.pop(context.Background()) require.Equal(t, block.ToBlockData(), readyBlockData) } diff --git a/dot/sync/tip_syncer_test.go b/dot/sync/tip_syncer_test.go new file mode 100644 index 0000000000..097f0dac75 --- /dev/null +++ b/dot/sync/tip_syncer_test.go @@ -0,0 +1,401 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package sync + +import ( + "testing" + + "github.com/ChainSafe/gossamer/dot/network" + "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/common" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" +) + +func Test_tipSyncer_handleNewPeerState(t *testing.T) { + t.Parallel() + + type fields struct { + blockStateBuilder func(ctrl *gomock.Controller) BlockState + pendingBlocks DisjointBlockSet + readyBlocks *blockQueue + } + tests := map[string]struct { + fields fields + peerState *peerState + want *worker + err error + }{ + "peer state number < final block number": { + fields: fields{ + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetHighestFinalisedHeader().Return(&types.Header{ + Number: 2, + }, nil) + return mockBlockState + }, + }, + peerState: &peerState{number: 1}, + want: nil, + }, + "base state": { + fields: fields{ + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetHighestFinalisedHeader().Return(&types.Header{ + Number: 2, + }, nil) + return mockBlockState + }, + }, + peerState: &peerState{number: 3}, + want: &worker{ + startNumber: uintPtr(3), + targetNumber: uintPtr(3), + requestData: bootstrapRequestData, + }, + }, + } + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + s := &tipSyncer{ + blockState: tt.fields.blockStateBuilder(ctrl), + pendingBlocks: tt.fields.pendingBlocks, + readyBlocks: tt.fields.readyBlocks, + } + got, err := s.handleNewPeerState(tt.peerState) + if tt.err != nil { + assert.EqualError(t, err, tt.err.Error()) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_tipSyncer_handleTick(t *testing.T) { + t.Parallel() + + type fields struct { + blockStateBuilder func(ctrl *gomock.Controller) BlockState + pendingBlocksBuilder func(ctrl *gomock.Controller) DisjointBlockSet + readyBlocks *blockQueue + } + tests := map[string]struct { + fields fields + want []*worker + err error + }{ + "base case": { + fields: fields{ + pendingBlocksBuilder: func(ctrl *gomock.Controller) DisjointBlockSet { + mockDisjointBlockSet := NewMockDisjointBlockSet(ctrl) + mockDisjointBlockSet.EXPECT().size().Return(1).Times(2) + mockDisjointBlockSet.EXPECT().getBlocks().Return([]*pendingBlock{ + {number: 2}, + {number: 3}, + {number: 4, + header: &types.Header{ + Number: 4, + }, + }, + {number: 5, + header: &types.Header{ + Number: 5, + }, + body: &types.Body{}, + }, + }) + mockDisjointBlockSet.EXPECT().removeBlock(common.Hash{}) + return mockDisjointBlockSet + }, + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetHighestFinalisedHeader().Return(&types.Header{ + Number: 2, + }, nil) + mockBlockState.EXPECT().HasHeader(common.Hash{}).Return(false, nil) + return mockBlockState + }, + readyBlocks: newBlockQueue(3), + }, + want: []*worker{ + { + startNumber: uintPtr(3), + targetNumber: uintPtr(2), + targetHash: common.Hash{5, 189, 204, 69, 79, 96, 160, 141, 66, 125, 5, 231, 241, + 159, 36, 15, 220, 57, 31, 87, 10, 183, 111, 203, 150, 236, 202, 11, 88, 35, 211, 191}, + pendingBlock: &pendingBlock{number: 3}, + requestData: bootstrapRequestData, + direction: network.Descending, + }, + { + startNumber: uintPtr(4), + targetNumber: uintPtr(4), + pendingBlock: &pendingBlock{ + number: 4, + header: &types.Header{ + Number: 4, + }, + }, + requestData: network.RequestedDataBody + network.RequestedDataJustification, + }, + { + startNumber: uintPtr(4), + targetNumber: uintPtr(2), + direction: network.Descending, + pendingBlock: &pendingBlock{ + number: 5, + header: &types.Header{ + Number: 5, + }, + body: &types.Body{}, + }, + requestData: bootstrapRequestData, + }, + }, + err: nil, + }, + } + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + s := &tipSyncer{ + blockState: tt.fields.blockStateBuilder(ctrl), + pendingBlocks: tt.fields.pendingBlocksBuilder(ctrl), + readyBlocks: tt.fields.readyBlocks, + } + got, err := s.handleTick() + if tt.err != nil { + assert.EqualError(t, err, tt.err.Error()) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_tipSyncer_handleWorkerResult(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + blockStateBuilder func(ctrl *gomock.Controller) BlockState + res *worker + want *worker + err error + }{ + "worker error is nil": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + return NewMockBlockState(ctrl) + }, + res: &worker{}, + want: nil, + err: nil, + }, + "worker error is error unknown parent": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + return NewMockBlockState(ctrl) + }, + res: &worker{ + err: &workerError{ + err: errUnknownParent, + }, + }, + want: nil, + err: nil, + }, + "ascending, target number < finalised number": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetHighestFinalisedHeader().Return(&types.Header{ + Number: 2, + }, nil) + return mockBlockState + }, + res: &worker{ + targetNumber: uintPtr(1), + direction: network.Ascending, + err: &workerError{}, + }, + want: nil, + err: nil, + }, + "ascending, start number < finalised number": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetHighestFinalisedHeader().Return(&types.Header{ + Number: 2, + }, nil) + return mockBlockState + }, + res: &worker{ + startNumber: uintPtr(1), + targetNumber: uintPtr(3), + direction: network.Ascending, + err: &workerError{}, + }, + want: &worker{ + startNumber: uintPtr(3), + targetNumber: uintPtr(3), + }, + err: nil, + }, + "descending, start number < finalised number": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetHighestFinalisedHeader().Return(&types.Header{ + Number: 2, + }, nil) + return mockBlockState + }, + res: &worker{ + startNumber: uintPtr(1), + direction: network.Descending, + err: &workerError{}, + }, + want: nil, + err: nil, + }, + "descending, target number < finalised number": { + blockStateBuilder: func(ctrl *gomock.Controller) BlockState { + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetHighestFinalisedHeader().Return(&types.Header{ + Number: 2, + }, nil) + return mockBlockState + }, + res: &worker{ + startNumber: uintPtr(3), + targetNumber: uintPtr(1), + direction: network.Descending, + err: &workerError{}, + }, + want: &worker{ + startNumber: uintPtr(3), + targetNumber: uintPtr(3), + direction: network.Descending, + }, + err: nil, + }, + } + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + s := &tipSyncer{ + blockState: tt.blockStateBuilder(ctrl), + } + got, err := s.handleWorkerResult(tt.res) + if tt.err != nil { + assert.EqualError(t, err, tt.err.Error()) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_tipSyncer_hasCurrentWorker(t *testing.T) { + t.Parallel() + + type args struct { + w *worker + workers map[uint64]*worker + } + tests := map[string]struct { + args args + want bool + }{ + "worker nil": { + want: true, + }, + "ascending, false": { + args: args{ + w: &worker{ + direction: network.Ascending, + startNumber: uintPtr(2), + targetNumber: uintPtr(2), + }, + workers: map[uint64]*worker{ + 1: { + direction: network.Ascending, + targetNumber: uintPtr(3), + startNumber: uintPtr(3), + }, + }, + }, + want: false, + }, + "ascending, true": { + args: args{ + w: &worker{ + direction: network.Ascending, + startNumber: uintPtr(2), + targetNumber: uintPtr(2), + }, + workers: map[uint64]*worker{ + 1: { + direction: network.Ascending, + targetNumber: uintPtr(3), + startNumber: uintPtr(1), + }, + }, + }, + want: true, + }, + "descending, false": { + args: args{ + w: &worker{ + direction: network.Descending, + startNumber: uintPtr(2), + targetNumber: uintPtr(2), + }, + workers: map[uint64]*worker{ + 1: { + startNumber: uintPtr(3), + targetNumber: uintPtr(3), + direction: network.Descending, + }, + }, + }, + want: false, + }, + "descending, true": { + args: args{ + w: &worker{ + direction: network.Descending, + startNumber: uintPtr(2), + targetNumber: uintPtr(2), + }, + workers: map[uint64]*worker{ + 1: { + startNumber: uintPtr(3), + targetNumber: uintPtr(1), + direction: network.Descending, + }, + }, + }, + want: true, + }, + } + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + t.Parallel() + s := &tipSyncer{} + got := s.hasCurrentWorker(tt.args.w, tt.args.workers) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/dot/telemetry/mailer_test.go b/dot/telemetry/mailer_test.go index f76b5e020f..8b4b414dfd 100644 --- a/dot/telemetry/mailer_test.go +++ b/dot/telemetry/mailer_test.go @@ -221,6 +221,7 @@ func TestListenerConcurrency(t *testing.T) { } func TestTelemetryMarshalMessage(t *testing.T) { + t.Parallel() tests := map[string]struct { message Message expected string