From e1f7f96cb8cb7e82156f7333b9a0704ab8151fd2 Mon Sep 17 00:00:00 2001 From: JimboJ <40345116+jimjbrettj@users.noreply.github.com> Date: Fri, 14 Jan 2022 14:44:23 -0700 Subject: [PATCH] Jimmy/babe unit tests (#2127) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(pkg/scale): return error if `Result` already has an assigned value (#2143) * fix: return error if result already has an assigned value * chore: include unit test * chore: fix typo `ErrResultAlreadySet` * chore: remove unneeded `require.Error` * chore: fix undeclared name * chore: remove packaged scope var to avoid problems with result type * chore: fix result.Set error at offchain test * init * migrate current tests to integration tests * fix linting * test line endings * remove test comments * test getSecondarySlotAuthor * secondary unit tests * calculate threshold tests * remove logs * test checkPrimaryThreshold * test babe/crypto * getAuthorityIndex test * babe mocks with mockgen * test verifyPrimarySlotWinner * test verifyPreruntime Digest * WIP/Test cases for verify authorship right * finish hitting test cases for equivocation * clean up naming for verifyBlockAuthor test * create helper for making verifier * use helpers to clean up verifyAuthorship test * clean up mocks * getConfigData test * test getVerifierInfo * remove logs * test first block cases for block verification * test VerifyBlock * finish first half of babe unit tests * remove coverage files * remove accidental diff * remove accidental diff again * fix linting * remove testing aliases * make calculate threshold error global * add go generate comment for babe state TODO/make sure ok form * fix mock generation * add newline to end of build integration test * newlines * wip/newline * test line endings * wip * remove crypto integration test * change threshold err message * wip * test * remove test comments * clean up * fix build error * CR feedback * more CR feedback * change threshold error name * remove empty fields * change how mockgen used * remove test log * cr feedback * remove errors from helper funcs * final helper fix * rebase and lint * CR feedback * lint Co-authored-by: Eclésio Junior --- ...{babe_test.go => babe_integration_test.go} | 4 +- ...uild_test.go => build_integration_test.go} | 5 +- lib/babe/crypto.go | 3 + lib/babe/crypto_test.go | 194 ++- ...poch_test.go => epoch_integration_test.go} | 3 + lib/babe/errors.go | 12 + lib/babe/mock_state_test.go | 839 ++++++++++ lib/babe/secondary_integration_test.go | 73 + lib/babe/secondary_test.go | 175 ++- lib/babe/state.go | 4 +- lib/babe/verify.go | 14 +- lib/babe/verify_integration_test.go | 409 +++++ lib/babe/verify_test.go | 1375 +++++++++++++---- 13 files changed, 2728 insertions(+), 382 deletions(-) rename lib/babe/{babe_test.go => babe_integration_test.go} (99%) rename lib/babe/{build_test.go => build_integration_test.go} (99%) rename lib/babe/{epoch_test.go => epoch_integration_test.go} (98%) create mode 100644 lib/babe/mock_state_test.go create mode 100644 lib/babe/secondary_integration_test.go create mode 100644 lib/babe/verify_integration_test.go diff --git a/lib/babe/babe_test.go b/lib/babe/babe_integration_test.go similarity index 99% rename from lib/babe/babe_test.go rename to lib/babe/babe_integration_test.go index 971d5001f9..9cc209da06 100644 --- a/lib/babe/babe_test.go +++ b/lib/babe/babe_integration_test.go @@ -1,6 +1,9 @@ // Copyright 2021 ChainSafe Systems (ON) // SPDX-License-Identifier: LGPL-3.0-only +//go:build integration +// +build integration + package babe import ( @@ -39,7 +42,6 @@ var ( keyring, _ = keystore.NewSr25519Keyring() maxThreshold = scale.MaxUint128 - minThreshold = &scale.Uint128{} genesisHeader *types.Header emptyHeader = &types.Header{ diff --git a/lib/babe/build_test.go b/lib/babe/build_integration_test.go similarity index 99% rename from lib/babe/build_test.go rename to lib/babe/build_integration_test.go index 022900ca11..5fb4bc5adf 100644 --- a/lib/babe/build_test.go +++ b/lib/babe/build_integration_test.go @@ -1,6 +1,9 @@ // Copyright 2021 ChainSafe Systems (ON) // SPDX-License-Identifier: LGPL-3.0-only +//go:build integration +// +build integration + package babe import ( @@ -392,7 +395,7 @@ func TestBuildBlock_failing(t *testing.T) { } // create proof that we can authorize this block - babeService.epochData.threshold = minThreshold + babeService.epochData.threshold = &scale.Uint128{} var slotNumber uint64 = 1 outAndProof, err := babeService.runLottery(slotNumber, testEpochIndex) diff --git a/lib/babe/crypto.go b/lib/babe/crypto.go index f87a4800d2..f3dfe4d2ac 100644 --- a/lib/babe/crypto.go +++ b/lib/babe/crypto.go @@ -87,6 +87,9 @@ func checkPrimaryThreshold(randomness Randomness, // equation: threshold = 2^128 * (1 - (1-c)^(1/len(authorities)) // see https://github.com/paritytech/substrate/blob/master/client/consensus/babe/src/authorship.rs#L44 func CalculateThreshold(C1, C2 uint64, numAuths int) (*scale.Uint128, error) { + if C1 == 0 || C2 == 0 { + return nil, ErrThresholdOneIsZero + } c := float64(C1) / float64(C2) if c > 1 { return nil, errors.New("invalid C1/C2: greater than 1") diff --git a/lib/babe/crypto_test.go b/lib/babe/crypto_test.go index 48685fe2b6..570d584df8 100644 --- a/lib/babe/crypto_test.go +++ b/lib/babe/crypto_test.go @@ -4,46 +4,170 @@ package babe import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestRunLottery(t *testing.T) { - babeService := createTestService(t, nil) - - babeService.epochData.threshold = maxThreshold - - outAndProof, err := babeService.runLottery(0, testEpochIndex) - require.NoError(t, err) - require.NotNil(t, outAndProof) -} + "errors" -func TestRunLottery_False(t *testing.T) { - babeService := createTestService(t, nil) - babeService.epochData.threshold = minThreshold + "github.com/ChainSafe/gossamer/lib/crypto/sr25519" + "github.com/ChainSafe/gossamer/lib/keystore" + "github.com/ChainSafe/gossamer/pkg/scale" + "github.com/stretchr/testify/assert" - outAndProof, err := babeService.runLottery(0, testEpochIndex) - require.ErrorIs(t, err, errOverPrimarySlotThreshold) - require.Nil(t, outAndProof) -} + "testing" +) func TestCalculateThreshold(t *testing.T) { - // C = 1 - var C1 uint64 = 1 - var C2 uint64 = 1 - - expected := maxThreshold - - threshold, err := CalculateThreshold(C1, C2, 3) - require.NoError(t, err) - require.Equal(t, expected, threshold) + type args struct { + C1 uint64 + C2 uint64 + numAuths int + } + tests := []struct { + name string + args args + exp *scale.Uint128 + expErr error + }{ + { + name: "happy path", + args: args{ + C1: 1, + C2: 2, + numAuths: 3, + }, + exp: &scale.Uint128{Upper: 0x34d00ad6148e1800, Lower: 0x0}, + }, + { + name: "0 value input", + args: args{ + C1: 0, + C2: 0, + numAuths: 0, + }, + expErr: ErrThresholdOneIsZero, + }, + { + name: "C1 > C2", + args: args{ + C1: 5, + C2: 2, + numAuths: 0, + }, + expErr: errors.New("invalid C1/C2: greater than 1"), + }, + { + name: "max threshold", + args: args{ + C1: 1, + C2: 1, + numAuths: 3, + }, + exp: scale.MaxUint128, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + res, err := CalculateThreshold(tt.args.C1, tt.args.C2, tt.args.numAuths) + if tt.expErr != nil { + assert.EqualError(t, err, tt.expErr.Error()) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tt.exp, res) + }) + } } -func TestCalculateThreshold_Failing(t *testing.T) { - var C1 uint64 = 5 - var C2 uint64 = 4 +func Test_checkPrimaryThreshold(t *testing.T) { + keyring, _ := keystore.NewSr25519Keyring() + aliceKeypair := keyring.Alice().(*sr25519.Keypair) + type args struct { + randomness Randomness + slot uint64 + epoch uint64 + output [sr25519.VRFOutputLength]byte + threshold *scale.Uint128 + pub *sr25519.PublicKey + } + tests := []struct { + name string + args args + exp bool + }{ + { + name: "happy path true", + args: args{ + threshold: scale.MaxUint128, + pub: aliceKeypair.Public().(*sr25519.PublicKey), + }, + exp: true, + }, + { + name: "happy path false", + args: args{ + threshold: &scale.Uint128{}, + pub: aliceKeypair.Public().(*sr25519.PublicKey), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + res, err := checkPrimaryThreshold(tt.args.randomness, tt.args.slot, tt.args.epoch, + tt.args.output, tt.args.threshold, tt.args.pub) + assert.NoError(t, err) + assert.Equal(t, tt.exp, res) + }) + } +} - _, err := CalculateThreshold(C1, C2, 3) - require.NotNil(t, err) +func Test_claimPrimarySlot(t *testing.T) { + keyring, _ := keystore.NewSr25519Keyring() + type args struct { + randomness Randomness + slot uint64 + epoch uint64 + threshold *scale.Uint128 + keypair *sr25519.Keypair + } + tests := []struct { + name string + args args + exp *VrfOutputAndProof + expErr error + }{ + { + name: "authority not authorized", + args: args{ + slot: 1, + epoch: 2, + threshold: &scale.Uint128{}, + keypair: keyring.Alice().(*sr25519.Keypair), + }, + expErr: errors.New("cannot claim slot, over primary threshold: for slot 1, epoch 2 and threshold 0"), + }, + { + name: "authority authorized", + args: args{ + slot: 1, + epoch: 2, + threshold: scale.MaxUint128, + keypair: keyring.Alice().(*sr25519.Keypair), + }, + exp: &VrfOutputAndProof{ + output: [32]uint8{0x80, 0xf0, 0x8a, 0x7d, 0xa1, 0x71, 0x77, 0xdc, 0x7, 0x7f, 0x6, 0xd5, 0xc1, 0x5d, 0x90, + 0x4f, 0x64, 0x21, 0xb6, 0x1d, 0x1c, 0xa8, 0x55, 0x3a, 0x97, 0x1a, 0xbb, 0xf3, 0x35, 0x12, 0x25, 0x18}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + res, err := claimPrimarySlot(tt.args.randomness, tt.args.slot, tt.args.epoch, tt.args.threshold, tt.args.keypair) + if tt.expErr != nil { + assert.EqualError(t, err, tt.expErr.Error()) + } else { + assert.NoError(t, err) + } + if tt.exp != nil && res != nil { + assert.Equal(t, tt.exp.output, res.output) + } + }) + } } diff --git a/lib/babe/epoch_test.go b/lib/babe/epoch_integration_test.go similarity index 98% rename from lib/babe/epoch_test.go rename to lib/babe/epoch_integration_test.go index 39876134ee..a35b838e52 100644 --- a/lib/babe/epoch_test.go +++ b/lib/babe/epoch_integration_test.go @@ -1,6 +1,9 @@ // Copyright 2021 ChainSafe Systems (ON) // SPDX-License-Identifier: LGPL-3.0-only +//go:build integration +// +build integration + package babe import ( diff --git a/lib/babe/errors.go b/lib/babe/errors.go index 7cee05e194..e1d0cffab9 100644 --- a/lib/babe/errors.go +++ b/lib/babe/errors.go @@ -57,6 +57,9 @@ var ( // ErrNotAuthority is returned when trying to perform authority functions when not an authority ErrNotAuthority = errors.New("node is not an authority") + // ErrThresholdOneIsZero is returned when one of or both parameters to CalculateThreshold is zero + ErrThresholdOneIsZero = errors.New("numerator or denominator cannot be 0") + errNilBlockImportHandler = errors.New("cannot have nil BlockImportHandler") errNilBlockState = errors.New("cannot have nil BlockState") errNilEpochState = errors.New("cannot have nil EpochState") @@ -67,6 +70,15 @@ var ( errFirstBlockTimeout = errors.New("timed out waiting for first block") errChannelClosed = errors.New("block notifier channel was closed") errOverPrimarySlotThreshold = errors.New("cannot claim slot, over primary threshold") + errNoConfigData = errors.New("cannot find ConfigData for epoch") + errGetEpochData = errors.New("get epochData error") + errFailedFinalisation = errors.New("failed to check finalisation") + errMissingDigest = errors.New("chain head missing digest") + errSetFirstSlot = errors.New("set first slot error") + errGetEpoch = errors.New("get epoch error") + errSkipVerify = errors.New("skipVerify error") + errMissingDigestItems = errors.New("block header is missing digest items") + errDescendant = errors.New("descendant err") other Other invalidCustom InvalidCustom diff --git a/lib/babe/mock_state_test.go b/lib/babe/mock_state_test.go new file mode 100644 index 0000000000..9f91b38f9b --- /dev/null +++ b/lib/babe/mock_state_test.go @@ -0,0 +1,839 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ChainSafe/gossamer/lib/babe (interfaces: BlockState,ImportedBlockNotifierManager,StorageState,TransactionState,EpochState,DigestHandler,BlockImportHandler) + +// Package babe is a generated GoMock package. +package babe + +import ( + big "math/big" + reflect "reflect" + time "time" + + 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" + transaction "github.com/ChainSafe/gossamer/lib/transaction" + gomock "github.com/golang/mock/gomock" +) + +// 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) +} + +// BestBlock mocks base method. +func (m *MockBlockState) BestBlock() (*types.Block, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BestBlock") + ret0, _ := ret[0].(*types.Block) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BestBlock indicates an expected call of BestBlock. +func (mr *MockBlockStateMockRecorder) BestBlock() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BestBlock", reflect.TypeOf((*MockBlockState)(nil).BestBlock)) +} + +// 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() (*big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BestBlockNumber") + ret0, _ := ret[0].(*big.Int) + 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)) +} + +// FreeImportedBlockNotifierChannel mocks base method. +func (m *MockBlockState) FreeImportedBlockNotifierChannel(arg0 chan *types.Block) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "FreeImportedBlockNotifierChannel", arg0) +} + +// FreeImportedBlockNotifierChannel indicates an expected call of FreeImportedBlockNotifierChannel. +func (mr *MockBlockStateMockRecorder) FreeImportedBlockNotifierChannel(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FreeImportedBlockNotifierChannel", reflect.TypeOf((*MockBlockState)(nil).FreeImportedBlockNotifierChannel), arg0) +} + +// GenesisHash mocks base method. +func (m *MockBlockState) GenesisHash() common.Hash { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GenesisHash") + ret0, _ := ret[0].(common.Hash) + return ret0 +} + +// GenesisHash indicates an expected call of GenesisHash. +func (mr *MockBlockStateMockRecorder) GenesisHash() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GenesisHash", reflect.TypeOf((*MockBlockState)(nil).GenesisHash)) +} + +// GetAllBlocksAtDepth mocks base method. +func (m *MockBlockState) GetAllBlocksAtDepth(arg0 common.Hash) []common.Hash { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAllBlocksAtDepth", arg0) + ret0, _ := ret[0].([]common.Hash) + return ret0 +} + +// GetAllBlocksAtDepth indicates an expected call of GetAllBlocksAtDepth. +func (mr *MockBlockStateMockRecorder) GetAllBlocksAtDepth(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllBlocksAtDepth", reflect.TypeOf((*MockBlockState)(nil).GetAllBlocksAtDepth), arg0) +} + +// GetArrivalTime mocks base method. +func (m *MockBlockState) GetArrivalTime(arg0 common.Hash) (time.Time, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetArrivalTime", arg0) + ret0, _ := ret[0].(time.Time) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetArrivalTime indicates an expected call of GetArrivalTime. +func (mr *MockBlockStateMockRecorder) GetArrivalTime(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetArrivalTime", reflect.TypeOf((*MockBlockState)(nil).GetArrivalTime), 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 *big.Int) (*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) +} + +// GetFinalisedHeader mocks base method. +func (m *MockBlockState) GetFinalisedHeader(arg0, arg1 uint64) (*types.Header, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFinalisedHeader", arg0, arg1) + ret0, _ := ret[0].(*types.Header) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFinalisedHeader indicates an expected call of GetFinalisedHeader. +func (mr *MockBlockStateMockRecorder) GetFinalisedHeader(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFinalisedHeader", reflect.TypeOf((*MockBlockState)(nil).GetFinalisedHeader), arg0, arg1) +} + +// 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) +} + +// GetImportedBlockNotifierChannel mocks base method. +func (m *MockBlockState) GetImportedBlockNotifierChannel() chan *types.Block { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetImportedBlockNotifierChannel") + ret0, _ := ret[0].(chan *types.Block) + return ret0 +} + +// GetImportedBlockNotifierChannel indicates an expected call of GetImportedBlockNotifierChannel. +func (mr *MockBlockStateMockRecorder) GetImportedBlockNotifierChannel() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImportedBlockNotifierChannel", reflect.TypeOf((*MockBlockState)(nil).GetImportedBlockNotifierChannel)) +} + +// 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) +} + +// GetSlotForBlock mocks base method. +func (m *MockBlockState) GetSlotForBlock(arg0 common.Hash) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSlotForBlock", arg0) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSlotForBlock indicates an expected call of GetSlotForBlock. +func (mr *MockBlockStateMockRecorder) GetSlotForBlock(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSlotForBlock", reflect.TypeOf((*MockBlockState)(nil).GetSlotForBlock), 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) +} + +// NumberIsFinalised mocks base method. +func (m *MockBlockState) NumberIsFinalised(arg0 *big.Int) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NumberIsFinalised", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NumberIsFinalised indicates an expected call of NumberIsFinalised. +func (mr *MockBlockStateMockRecorder) NumberIsFinalised(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NumberIsFinalised", reflect.TypeOf((*MockBlockState)(nil).NumberIsFinalised), arg0) +} + +// 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) +} + +// MockImportedBlockNotifierManager is a mock of ImportedBlockNotifierManager interface. +type MockImportedBlockNotifierManager struct { + ctrl *gomock.Controller + recorder *MockImportedBlockNotifierManagerMockRecorder +} + +// MockImportedBlockNotifierManagerMockRecorder is the mock recorder for MockImportedBlockNotifierManager. +type MockImportedBlockNotifierManagerMockRecorder struct { + mock *MockImportedBlockNotifierManager +} + +// NewMockImportedBlockNotifierManager creates a new mock instance. +func NewMockImportedBlockNotifierManager(ctrl *gomock.Controller) *MockImportedBlockNotifierManager { + mock := &MockImportedBlockNotifierManager{ctrl: ctrl} + mock.recorder = &MockImportedBlockNotifierManagerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockImportedBlockNotifierManager) EXPECT() *MockImportedBlockNotifierManagerMockRecorder { + return m.recorder +} + +// FreeImportedBlockNotifierChannel mocks base method. +func (m *MockImportedBlockNotifierManager) FreeImportedBlockNotifierChannel(arg0 chan *types.Block) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "FreeImportedBlockNotifierChannel", arg0) +} + +// FreeImportedBlockNotifierChannel indicates an expected call of FreeImportedBlockNotifierChannel. +func (mr *MockImportedBlockNotifierManagerMockRecorder) FreeImportedBlockNotifierChannel(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FreeImportedBlockNotifierChannel", reflect.TypeOf((*MockImportedBlockNotifierManager)(nil).FreeImportedBlockNotifierChannel), arg0) +} + +// GetImportedBlockNotifierChannel mocks base method. +func (m *MockImportedBlockNotifierManager) GetImportedBlockNotifierChannel() chan *types.Block { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetImportedBlockNotifierChannel") + ret0, _ := ret[0].(chan *types.Block) + return ret0 +} + +// GetImportedBlockNotifierChannel indicates an expected call of GetImportedBlockNotifierChannel. +func (mr *MockImportedBlockNotifierManagerMockRecorder) GetImportedBlockNotifierChannel() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImportedBlockNotifierChannel", reflect.TypeOf((*MockImportedBlockNotifierManager)(nil).GetImportedBlockNotifierChannel)) +} + +// 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 +} + +// 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)) +} + +// 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 +} + +// Peek mocks base method. +func (m *MockTransactionState) Peek() *transaction.ValidTransaction { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Peek") + ret0, _ := ret[0].(*transaction.ValidTransaction) + return ret0 +} + +// Peek indicates an expected call of Peek. +func (mr *MockTransactionStateMockRecorder) Peek() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Peek", reflect.TypeOf((*MockTransactionState)(nil).Peek)) +} + +// Pop mocks base method. +func (m *MockTransactionState) Pop() *transaction.ValidTransaction { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Pop") + ret0, _ := ret[0].(*transaction.ValidTransaction) + return ret0 +} + +// Pop indicates an expected call of Pop. +func (mr *MockTransactionStateMockRecorder) Pop() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Pop", reflect.TypeOf((*MockTransactionState)(nil).Pop)) +} + +// Push mocks base method. +func (m *MockTransactionState) Push(arg0 *transaction.ValidTransaction) (common.Hash, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Push", arg0) + ret0, _ := ret[0].(common.Hash) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Push indicates an expected call of Push. +func (mr *MockTransactionStateMockRecorder) Push(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Push", reflect.TypeOf((*MockTransactionState)(nil).Push), arg0) +} + +// MockEpochState is a mock of EpochState interface. +type MockEpochState struct { + ctrl *gomock.Controller + recorder *MockEpochStateMockRecorder +} + +// MockEpochStateMockRecorder is the mock recorder for MockEpochState. +type MockEpochStateMockRecorder struct { + mock *MockEpochState +} + +// NewMockEpochState creates a new mock instance. +func NewMockEpochState(ctrl *gomock.Controller) *MockEpochState { + mock := &MockEpochState{ctrl: ctrl} + mock.recorder = &MockEpochStateMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockEpochState) EXPECT() *MockEpochStateMockRecorder { + return m.recorder +} + +// GetConfigData mocks base method. +func (m *MockEpochState) GetConfigData(arg0 uint64) (*types.ConfigData, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetConfigData", arg0) + ret0, _ := ret[0].(*types.ConfigData) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetConfigData indicates an expected call of GetConfigData. +func (mr *MockEpochStateMockRecorder) GetConfigData(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConfigData", reflect.TypeOf((*MockEpochState)(nil).GetConfigData), arg0) +} + +// GetCurrentEpoch mocks base method. +func (m *MockEpochState) GetCurrentEpoch() (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCurrentEpoch") + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCurrentEpoch indicates an expected call of GetCurrentEpoch. +func (mr *MockEpochStateMockRecorder) GetCurrentEpoch() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentEpoch", reflect.TypeOf((*MockEpochState)(nil).GetCurrentEpoch)) +} + +// GetEpochData mocks base method. +func (m *MockEpochState) GetEpochData(arg0 uint64) (*types.EpochData, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetEpochData", arg0) + ret0, _ := ret[0].(*types.EpochData) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetEpochData indicates an expected call of GetEpochData. +func (mr *MockEpochStateMockRecorder) GetEpochData(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEpochData", reflect.TypeOf((*MockEpochState)(nil).GetEpochData), arg0) +} + +// GetEpochForBlock mocks base method. +func (m *MockEpochState) GetEpochForBlock(arg0 *types.Header) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetEpochForBlock", arg0) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetEpochForBlock indicates an expected call of GetEpochForBlock. +func (mr *MockEpochStateMockRecorder) GetEpochForBlock(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEpochForBlock", reflect.TypeOf((*MockEpochState)(nil).GetEpochForBlock), arg0) +} + +// GetEpochFromTime mocks base method. +func (m *MockEpochState) GetEpochFromTime(arg0 time.Time) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetEpochFromTime", arg0) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetEpochFromTime indicates an expected call of GetEpochFromTime. +func (mr *MockEpochStateMockRecorder) GetEpochFromTime(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEpochFromTime", reflect.TypeOf((*MockEpochState)(nil).GetEpochFromTime), arg0) +} + +// GetEpochLength mocks base method. +func (m *MockEpochState) GetEpochLength() (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetEpochLength") + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetEpochLength indicates an expected call of GetEpochLength. +func (mr *MockEpochStateMockRecorder) GetEpochLength() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEpochLength", reflect.TypeOf((*MockEpochState)(nil).GetEpochLength)) +} + +// GetLatestConfigData mocks base method. +func (m *MockEpochState) GetLatestConfigData() (*types.ConfigData, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLatestConfigData") + ret0, _ := ret[0].(*types.ConfigData) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLatestConfigData indicates an expected call of GetLatestConfigData. +func (mr *MockEpochStateMockRecorder) GetLatestConfigData() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestConfigData", reflect.TypeOf((*MockEpochState)(nil).GetLatestConfigData)) +} + +// GetLatestEpochData mocks base method. +func (m *MockEpochState) GetLatestEpochData() (*types.EpochData, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLatestEpochData") + ret0, _ := ret[0].(*types.EpochData) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLatestEpochData indicates an expected call of GetLatestEpochData. +func (mr *MockEpochStateMockRecorder) GetLatestEpochData() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestEpochData", reflect.TypeOf((*MockEpochState)(nil).GetLatestEpochData)) +} + +// GetSlotDuration mocks base method. +func (m *MockEpochState) GetSlotDuration() (time.Duration, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSlotDuration") + ret0, _ := ret[0].(time.Duration) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSlotDuration indicates an expected call of GetSlotDuration. +func (mr *MockEpochStateMockRecorder) GetSlotDuration() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSlotDuration", reflect.TypeOf((*MockEpochState)(nil).GetSlotDuration)) +} + +// GetStartSlotForEpoch mocks base method. +func (m *MockEpochState) GetStartSlotForEpoch(arg0 uint64) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetStartSlotForEpoch", arg0) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetStartSlotForEpoch indicates an expected call of GetStartSlotForEpoch. +func (mr *MockEpochStateMockRecorder) GetStartSlotForEpoch(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStartSlotForEpoch", reflect.TypeOf((*MockEpochState)(nil).GetStartSlotForEpoch), arg0) +} + +// HasConfigData mocks base method. +func (m *MockEpochState) HasConfigData(arg0 uint64) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasConfigData", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HasConfigData indicates an expected call of HasConfigData. +func (mr *MockEpochStateMockRecorder) HasConfigData(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasConfigData", reflect.TypeOf((*MockEpochState)(nil).HasConfigData), arg0) +} + +// HasEpochData mocks base method. +func (m *MockEpochState) HasEpochData(arg0 uint64) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasEpochData", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HasEpochData indicates an expected call of HasEpochData. +func (mr *MockEpochStateMockRecorder) HasEpochData(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasEpochData", reflect.TypeOf((*MockEpochState)(nil).HasEpochData), arg0) +} + +// SetCurrentEpoch mocks base method. +func (m *MockEpochState) SetCurrentEpoch(arg0 uint64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetCurrentEpoch", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetCurrentEpoch indicates an expected call of SetCurrentEpoch. +func (mr *MockEpochStateMockRecorder) SetCurrentEpoch(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCurrentEpoch", reflect.TypeOf((*MockEpochState)(nil).SetCurrentEpoch), arg0) +} + +// SetEpochData mocks base method. +func (m *MockEpochState) SetEpochData(arg0 uint64, arg1 *types.EpochData) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetEpochData", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetEpochData indicates an expected call of SetEpochData. +func (mr *MockEpochStateMockRecorder) SetEpochData(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetEpochData", reflect.TypeOf((*MockEpochState)(nil).SetEpochData), arg0, arg1) +} + +// SetFirstSlot mocks base method. +func (m *MockEpochState) SetFirstSlot(arg0 uint64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetFirstSlot", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetFirstSlot indicates an expected call of SetFirstSlot. +func (mr *MockEpochStateMockRecorder) SetFirstSlot(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetFirstSlot", reflect.TypeOf((*MockEpochState)(nil).SetFirstSlot), arg0) +} + +// SkipVerify mocks base method. +func (m *MockEpochState) SkipVerify(arg0 *types.Header) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SkipVerify", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SkipVerify indicates an expected call of SkipVerify. +func (mr *MockEpochStateMockRecorder) SkipVerify(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SkipVerify", reflect.TypeOf((*MockEpochState)(nil).SkipVerify), arg0) +} + +// MockDigestHandler is a mock of DigestHandler interface. +type MockDigestHandler struct { + ctrl *gomock.Controller + recorder *MockDigestHandlerMockRecorder +} + +// MockDigestHandlerMockRecorder is the mock recorder for MockDigestHandler. +type MockDigestHandlerMockRecorder struct { + mock *MockDigestHandler +} + +// NewMockDigestHandler creates a new mock instance. +func NewMockDigestHandler(ctrl *gomock.Controller) *MockDigestHandler { + mock := &MockDigestHandler{ctrl: ctrl} + mock.recorder = &MockDigestHandlerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDigestHandler) EXPECT() *MockDigestHandlerMockRecorder { + return m.recorder +} + +// HandleDigests mocks base method. +func (m *MockDigestHandler) HandleDigests(arg0 *types.Header) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "HandleDigests", arg0) +} + +// HandleDigests indicates an expected call of HandleDigests. +func (mr *MockDigestHandlerMockRecorder) HandleDigests(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleDigests", reflect.TypeOf((*MockDigestHandler)(nil).HandleDigests), arg0) +} + +// 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 +} + +// HandleBlockProduced mocks base method. +func (m *MockBlockImportHandler) HandleBlockProduced(arg0 *types.Block, arg1 *storage.TrieState) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HandleBlockProduced", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// HandleBlockProduced indicates an expected call of HandleBlockProduced. +func (mr *MockBlockImportHandlerMockRecorder) HandleBlockProduced(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleBlockProduced", reflect.TypeOf((*MockBlockImportHandler)(nil).HandleBlockProduced), arg0, arg1) +} diff --git a/lib/babe/secondary_integration_test.go b/lib/babe/secondary_integration_test.go new file mode 100644 index 0000000000..7e64e300bd --- /dev/null +++ b/lib/babe/secondary_integration_test.go @@ -0,0 +1,73 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +//go:build integration +// +build integration + +package babe + +import ( + "testing" + + "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/crypto/sr25519" + + "github.com/stretchr/testify/require" +) + +func TestVerifySecondarySlotPlain(t *testing.T) { + err := verifySecondarySlotPlain(0, 77, 1, Randomness{}) + require.NoError(t, err) + + err = verifySecondarySlotPlain(0, 77, 2, Randomness{}) + require.NoError(t, err) + + const numAuths = 20 + numAuthorized := 0 + for i := 0; i < numAuths; i++ { + err = verifySecondarySlotPlain(uint32(i), 77, numAuths, Randomness{}) + if err == nil { + numAuthorized++ + } + } + + require.Equal(t, 1, numAuthorized, "only one block producer should be authorized per secondary slot") +} + +func createSecondaryVRFPreDigest(t *testing.T, + keypair *sr25519.Keypair, index uint32, + slot, epoch uint64, randomness Randomness, +) *types.BabeSecondaryVRFPreDigest { + transcript := makeTranscript(randomness, slot, epoch) + out, proof, err := keypair.VrfSign(transcript) + require.NoError(t, err) + + return types.NewBabeSecondaryVRFPreDigest(index, slot, out, proof) +} + +func TestVerifySecondarySlotVRF(t *testing.T) { + kp, err := sr25519.GenerateKeypair() + require.NoError(t, err) + + slot := uint64(77) + epoch := uint64(0) + + digest := createSecondaryVRFPreDigest(t, kp, 0, slot, epoch, Randomness{}) + + ok, err := verifySecondarySlotVRF(digest, kp.Public().(*sr25519.PublicKey), epoch, 1, Randomness{}) + require.NoError(t, err) + require.True(t, ok) + + const numAuths = 20 + numAuthorized := 0 + for i := 0; i < numAuths; i++ { + digest := createSecondaryVRFPreDigest(t, kp, uint32(i), slot, epoch, Randomness{}) + + ok, err = verifySecondarySlotVRF(digest, kp.Public().(*sr25519.PublicKey), epoch, 1, Randomness{}) + if err == nil && ok { + numAuthorized++ + } + } + + require.Equal(t, 1, numAuthorized, "only one block producer should be authorized per secondary slot") +} diff --git a/lib/babe/secondary_test.go b/lib/babe/secondary_test.go index b94d892d7b..6e48e117c4 100644 --- a/lib/babe/secondary_test.go +++ b/lib/babe/secondary_test.go @@ -9,62 +9,139 @@ import ( "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/crypto/sr25519" - "github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" ) -func TestVerifySecondarySlotPlain(t *testing.T) { - err := verifySecondarySlotPlain(0, 77, 1, Randomness{}) - require.NoError(t, err) - - err = verifySecondarySlotPlain(0, 77, 2, Randomness{}) - require.NoError(t, err) - - numAuths := 20 - numAuthorized := 0 - for i := 0; i < numAuths; i++ { - err = verifySecondarySlotPlain(uint32(i), 77, numAuths, Randomness{}) - if err == nil { - numAuthorized++ - } +func Test_getSecondarySlotAuthor(t *testing.T) { + type args struct { + slot uint64 + numAuths int + randomness Randomness + } + tests := []struct { + name string + args args + exp uint32 + }{ + { + name: "happy path", + args: args{ + slot: 21, + numAuths: 21, + }, + exp: 14, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + res, err := getSecondarySlotAuthor(tt.args.slot, tt.args.numAuths, tt.args.randomness) + assert.NoError(t, err) + assert.Equal(t, tt.exp, res) + }) } - - require.Equal(t, 1, numAuthorized, "only one block producer should be authorized per secondary slot") } -func createSecondaryVRFPreDigest(t *testing.T, - keypair *sr25519.Keypair, index uint32, - slot, epoch uint64, randomness Randomness, -) *types.BabeSecondaryVRFPreDigest { - transcript := makeTranscript(randomness, slot, epoch) - out, proof, err := keypair.VrfSign(transcript) - require.NoError(t, err) - - return types.NewBabeSecondaryVRFPreDigest(index, slot, out, proof) +func Test_verifySecondarySlotPlain(t *testing.T) { + type args struct { + authorityIndex uint32 + slot uint64 + numAuths int + randomness Randomness + } + tests := []struct { + name string + args args + expErr error + }{ + { + name: "happy path", + args: args{ + authorityIndex: 14, + slot: 21, + numAuths: 21, + }, + }, + { + name: "err path", + args: args{ + authorityIndex: 13, + slot: 21, + numAuths: 21, + }, + expErr: ErrBadSecondarySlotClaim, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := verifySecondarySlotPlain(tt.args.authorityIndex, tt.args.slot, tt.args.numAuths, tt.args.randomness) + if tt.expErr != nil { + assert.EqualError(t, err, tt.expErr.Error()) + } else { + assert.NoError(t, err) + } + }) + } } -func TestVerifySecondarySlotVRF(t *testing.T) { +func Test_verifySecondarySlotVRF(t *testing.T) { kp, err := sr25519.GenerateKeypair() - require.NoError(t, err) - - slot := uint64(77) - epoch := uint64(0) - - digest := createSecondaryVRFPreDigest(t, kp, 0, slot, epoch, Randomness{}) - - ok, err := verifySecondarySlotVRF(digest, kp.Public().(*sr25519.PublicKey), epoch, 1, Randomness{}) - require.NoError(t, err) - require.True(t, ok) - - numAuths := 20 - numAuthorized := 0 - for i := 0; i < numAuths; i++ { - digest := createSecondaryVRFPreDigest(t, kp, uint32(i), slot, epoch, Randomness{}) - - ok, err = verifySecondarySlotVRF(digest, kp.Public().(*sr25519.PublicKey), epoch, 1, Randomness{}) - if err == nil && ok { - numAuthorized++ - } + assert.NoError(t, err) + + transcript := makeTranscript(Randomness{}, 77, 0) + out, proof, err := kp.VrfSign(transcript) + assert.NoError(t, err) + + type args struct { + digest *types.BabeSecondaryVRFPreDigest + pk *sr25519.PublicKey + epoch uint64 + numAuths int + randomness Randomness + } + tests := []struct { + name string + args args + exp bool + expErr error + }{ + { + name: "happy path", + args: args{ + digest: types.NewBabeSecondaryVRFPreDigest(0, 77, out, proof), + pk: kp.Public().(*sr25519.PublicKey), + numAuths: 1, + }, + exp: true, + }, + { + name: "err ErrBadSecondarySlotClaim", + args: args{ + digest: types.NewBabeSecondaryVRFPreDigest(3, 77, out, proof), + pk: kp.Public().(*sr25519.PublicKey), + epoch: 77, + numAuths: 1, + }, + expErr: ErrBadSecondarySlotClaim, + }, + { + name: "false path", + args: args{ + digest: types.NewBabeSecondaryVRFPreDigest(0, 77, out, proof), + pk: kp.Public().(*sr25519.PublicKey), + epoch: 77, + numAuths: 1, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + res, err := verifySecondarySlotVRF(tt.args.digest, tt.args.pk, tt.args.epoch, tt.args.numAuths, tt.args.randomness) + if tt.expErr != nil { + assert.EqualError(t, err, tt.expErr.Error()) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tt.exp, res) + }) } - - require.Equal(t, 1, numAuthorized, "only one block producer should be authorized per secondary slot") } diff --git a/lib/babe/state.go b/lib/babe/state.go index af9c9931b1..ae620ebe82 100644 --- a/lib/babe/state.go +++ b/lib/babe/state.go @@ -15,6 +15,8 @@ import ( "github.com/ChainSafe/gossamer/lib/transaction" ) +//go:generate mockgen -destination=./mock_state_test.go -package $GOPACKAGE . BlockState,ImportedBlockNotifierManager,StorageState,TransactionState,EpochState,DigestHandler,BlockImportHandler + // BlockState interface for block state methods type BlockState interface { BestBlockHash() common.Hash @@ -82,8 +84,6 @@ type DigestHandler interface { HandleDigests(*types.Header) } -//go:generate mockery --name BlockImportHandler --structname BlockImportHandler --case underscore --keeptree - // BlockImportHandler is the interface for the handler of new blocks type BlockImportHandler interface { HandleBlockProduced(block *types.Block, state *rtstorage.TrieState) error diff --git a/lib/babe/verify.go b/lib/babe/verify.go index 15fd8c8591..92d072c4b3 100644 --- a/lib/babe/verify.go +++ b/lib/babe/verify.go @@ -4,7 +4,6 @@ package babe import ( - "errors" "fmt" "math/big" "sync" @@ -139,6 +138,7 @@ func (v *VerificationManager) VerifyBlock(header *types.Header) error { // special case for block 1 - the network doesn't necessarily start in epoch 1. // if this happens, the database will be missing info for epochs before the first block. if header.Number.Cmp(big.NewInt(1)) == 0 { + block1IsFinal, err := v.blockState.NumberIsFinalised(big.NewInt(1)) if err != nil { return fmt.Errorf("failed to check if block 1 is finalised: %w", err) @@ -172,9 +172,9 @@ func (v *VerificationManager) VerifyBlock(header *types.Header) error { v.lock.Unlock() // SkipVerify is set to true only in the case where we have imported a state at a given height, // thus missing the epoch data for previous epochs. - skip, err2 := v.epochState.SkipVerify(header) - if err2 != nil { - return fmt.Errorf("failed to check if verification can be skipped: %w", err) + skip, skipErr := v.epochState.SkipVerify(header) + if skipErr != nil { + return fmt.Errorf("failed to check if verification can be skipped: %w", skipErr) } if skip { @@ -233,7 +233,7 @@ func (v *VerificationManager) getConfigData(epoch uint64) (*types.ConfigData, er } } - return nil, errors.New("cannot find ConfigData for epoch") + return nil, errNoConfigData } // verifier is a BABE verifier for a specific authority set, randomness, and threshold @@ -267,7 +267,7 @@ func (b *verifier) verifyAuthorshipRight(header *types.Header) error { // header should have 2 digest items (possibly more in the future) // first item should be pre-digest, second should be seal if len(header.Digest.Types) < 2 { - return fmt.Errorf("block header is missing digest items") + return errMissingDigestItems } logger.Tracef("beginning BABE authorship right verification for block %s", header.Hash()) @@ -283,7 +283,7 @@ func (b *verifier) verifyAuthorshipRight(header *types.Header) error { seal, ok := sealItem.Value().(types.SealDigest) if !ok { - return fmt.Errorf("first digest item is not pre-digest") + return fmt.Errorf("last digest item is not seal") } babePreDigest, err := b.verifyPreRuntimeDigest(&preDigest) diff --git a/lib/babe/verify_integration_test.go b/lib/babe/verify_integration_test.go new file mode 100644 index 0000000000..940d661253 --- /dev/null +++ b/lib/babe/verify_integration_test.go @@ -0,0 +1,409 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +//go:build integration +// +build integration + +package babe + +import ( + "errors" + "math/big" + "testing" + "time" + + "github.com/ChainSafe/gossamer/dot/state" + "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/internal/log" + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/crypto/sr25519" + "github.com/ChainSafe/gossamer/lib/genesis" + "github.com/ChainSafe/gossamer/pkg/scale" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" +) + +func newTestVerificationManager(t *testing.T, genCfg *types.BabeConfiguration) *VerificationManager { + testDatadirPath := t.TempDir() + + ctrl := gomock.NewController(t) + telemetryMock := NewMockClient(ctrl) + telemetryMock.EXPECT().SendMessage(gomock.Any()).AnyTimes() + + config := state.Config{ + Path: testDatadirPath, + LogLevel: log.Info, + Telemetry: telemetryMock, + } + dbSrv := state.NewService(config) + dbSrv.UseMemDB() + + if genCfg == nil { + genCfg = genesisBABEConfig + } + + gen, genTrie, genHeader := genesis.NewDevGenesisWithTrieAndHeader(t) + err := dbSrv.Initialise(gen, genHeader, genTrie) + require.NoError(t, err) + + err = dbSrv.Start() + require.NoError(t, err) + dbSrv.Epoch, err = state.NewEpochStateFromGenesis(dbSrv.DB(), dbSrv.Block, genCfg) + require.NoError(t, err) + + logger.Patch(log.SetLevel(defaultTestLogLvl)) + + vm, err := NewVerificationManager(dbSrv.Block, dbSrv.Epoch) + require.NoError(t, err) + return vm +} + +func TestVerificationManager_OnDisabled_InvalidIndex(t *testing.T) { + vm := newTestVerificationManager(t, nil) + + babeService := createTestService(t, nil) + block, _ := createTestBlock(t, babeService, genesisHeader, [][]byte{}, 1, testEpochIndex) + err := vm.SetOnDisabled(1, &block.Header) + require.Equal(t, err, ErrInvalidBlockProducerIndex) +} + +func TestVerificationManager_OnDisabled_NewDigest(t *testing.T) { + kp, err := sr25519.GenerateKeypair() + require.NoError(t, err) + + cfg := &ServiceConfig{ + Keypair: kp, + } + + babeService := createTestService(t, cfg) + + vm := newTestVerificationManager(t, nil) + vm.epochInfo[testEpochIndex] = &verifierInfo{ + authorities: babeService.epochData.authorities, + threshold: babeService.epochData.threshold, + randomness: babeService.epochData.randomness, + } + + parent, _ := babeService.blockState.BestBlockHeader() + block, _ := createTestBlock(t, babeService, parent, [][]byte{}, 1, testEpochIndex) + err = vm.blockState.AddBlock(block) + require.NoError(t, err) + + err = vm.SetOnDisabled(0, &block.Header) + require.NoError(t, err) + + // create an OnDisabled change on a different branch + block, _ = createTestBlock(t, babeService, parent, [][]byte{}, 2, testEpochIndex) + err = vm.blockState.AddBlock(block) + require.NoError(t, err) + + err = vm.SetOnDisabled(0, &block.Header) + require.NoError(t, err) +} + +func TestVerificationManager_OnDisabled_DuplicateDigest(t *testing.T) { + kp, err := sr25519.GenerateKeypair() + require.NoError(t, err) + + cfg := &ServiceConfig{ + Keypair: kp, + } + + babeService := createTestService(t, cfg) + + vm := newTestVerificationManager(t, nil) + vm.epochInfo[testEpochIndex] = &verifierInfo{ + authorities: babeService.epochData.authorities, + threshold: babeService.epochData.threshold, + randomness: babeService.epochData.randomness, + } + + block, _ := createTestBlock(t, babeService, genesisHeader, [][]byte{}, 1, testEpochIndex) + err = vm.blockState.AddBlock(block) + require.NoError(t, err) + + err = vm.SetOnDisabled(0, &block.Header) + require.NoError(t, err) + + // create an OnDisabled change on a different branch + block2, _ := createTestBlock(t, babeService, &block.Header, [][]byte{}, 2, testEpochIndex) + err = vm.blockState.AddBlock(block2) + require.NoError(t, err) + + err = vm.SetOnDisabled(0, &block2.Header) + require.Equal(t, ErrAuthorityAlreadyDisabled, err) +} + +func TestVerificationManager_VerifyBlock_Ok(t *testing.T) { + babeService := createTestService(t, nil) + rt, err := babeService.blockState.GetRuntime(nil) + require.NoError(t, err) + + cfg, err := rt.BabeConfiguration() + require.NoError(t, err) + + cfg.GenesisAuthorities = types.AuthoritiesToRaw(babeService.epochData.authorities) + cfg.C1 = 1 + cfg.C2 = 1 + + vm := newTestVerificationManager(t, cfg) + + block, _ := createTestBlock(t, babeService, genesisHeader, [][]byte{}, 1, testEpochIndex) + err = vm.VerifyBlock(&block.Header) + require.NoError(t, err) +} + +func TestVerificationManager_VerifyBlock_Secondary(t *testing.T) { + babeService := createTestService(t, nil) + rt, err := babeService.blockState.GetRuntime(nil) + require.NoError(t, err) + + cfg, err := rt.BabeConfiguration() + require.NoError(t, err) + + cfg.GenesisAuthorities = types.AuthoritiesToRaw(babeService.epochData.authorities) + cfg.C1 = 1 + cfg.C2 = 1 + cfg.SecondarySlots = 0 + + vm := newTestVerificationManager(t, cfg) + + kp, err := sr25519.GenerateKeypair() + require.NoError(t, err) + + dig := createSecondaryVRFPreDigest(t, kp, 0, uint64(0), uint64(0), Randomness{}) + + bd := types.NewBabeDigest() + err = bd.Set(dig) + require.NoError(t, err) + + bdEnc, err := scale.Marshal(bd) + require.NoError(t, err) + + // create pre-digest + preDigest := &types.PreRuntimeDigest{ + ConsensusEngineID: types.BabeEngineID, + Data: bdEnc, + } + + // create new block header + number := big.NewInt(1) + digest := types.NewDigest() + err = digest.Add(*preDigest) + require.NoError(t, err) + + // create seal and add to digest + seal := &types.SealDigest{ + ConsensusEngineID: types.BabeEngineID, + Data: []byte{0}, + } + require.NoError(t, err) + + err = digest.Add(*seal) + require.NoError(t, err) + + header, err := types.NewHeader(common.Hash{}, common.Hash{}, common.Hash{}, number, digest) + require.NoError(t, err) + + block := types.Block{ + Header: *header, + Body: nil, + } + err = vm.VerifyBlock(&block.Header) + require.EqualError(t, err, "failed to verify pre-runtime digest: could not verify slot claim VRF proof") +} + +func TestVerificationManager_VerifyBlock_MultipleEpochs(t *testing.T) { + babeService := createTestService(t, nil) + rt, err := babeService.blockState.GetRuntime(nil) + require.NoError(t, err) + + cfg, err := rt.BabeConfiguration() + require.NoError(t, err) + + cfg.GenesisAuthorities = types.AuthoritiesToRaw(babeService.epochData.authorities) + cfg.C1 = 1 + cfg.C2 = 1 + + vm := newTestVerificationManager(t, cfg) + + futureEpoch := uint64(5) + + err = vm.epochState.(*state.EpochState).SetEpochData(futureEpoch, &types.EpochData{ + Authorities: babeService.epochData.authorities, + Randomness: babeService.epochData.randomness, + }) + require.NoError(t, err) + + // create block in future epoch + block1, _ := createTestBlock(t, babeService, genesisHeader, [][]byte{}, cfg.EpochLength*futureEpoch+1, futureEpoch) + block2, _ := createTestBlock(t, babeService, &block1.Header, [][]byte{}, cfg.EpochLength*futureEpoch+2, futureEpoch) + + err = vm.VerifyBlock(&block2.Header) + require.NoError(t, err) + + // create block in epoch 1 + block, _ := createTestBlock(t, babeService, genesisHeader, [][]byte{}, cfg.EpochLength-10, testEpochIndex) + + err = vm.VerifyBlock(&block.Header) + require.NoError(t, err) +} + +func TestVerificationManager_VerifyBlock_InvalidBlockOverThreshold(t *testing.T) { + babeService := createTestService(t, nil) + rt, err := babeService.blockState.GetRuntime(nil) + require.NoError(t, err) + + cfg, err := rt.BabeConfiguration() + require.NoError(t, err) + + cfg.GenesisAuthorities = types.AuthoritiesToRaw(babeService.epochData.authorities) + cfg.C1 = 1 + cfg.C2 = 100 + + vm := newTestVerificationManager(t, cfg) + + block, _ := createTestBlock(t, babeService, genesisHeader, [][]byte{}, 1, testEpochIndex) + + err = vm.VerifyBlock(&block.Header) + require.Equal(t, ErrVRFOutputOverThreshold, errors.Unwrap(err)) +} + +func TestVerificationManager_VerifyBlock_InvalidBlockAuthority(t *testing.T) { + babeService := createTestService(t, nil) + rt, err := babeService.blockState.GetRuntime(nil) + require.NoError(t, err) + + cfg, err := rt.BabeConfiguration() + require.NoError(t, err) + + cfg.C1 = 1 + cfg.C2 = 1 + cfg.GenesisAuthorities = []types.AuthorityRaw{} + + vm := newTestVerificationManager(t, cfg) + + block, _ := createTestBlock(t, babeService, genesisHeader, [][]byte{}, 1, testEpochIndex) + + err = vm.VerifyBlock(&block.Header) + require.Equal(t, ErrInvalidBlockProducerIndex, errors.Unwrap(err)) +} + +func TestVerifyPimarySlotWinner(t *testing.T) { + kp, err := sr25519.GenerateKeypair() + require.NoError(t, err) + + cfg := &ServiceConfig{ + Keypair: kp, + } + + babeService := createTestService(t, cfg) + + // create proof that we can authorize this block + babeService.epochData.threshold = maxThreshold + babeService.epochData.authorityIndex = 0 + + builder, _ := NewBlockBuilder( + babeService.keypair, + babeService.transactionState, + babeService.blockState, + babeService.slotToProof, + babeService.epochData.authorityIndex, + ) + + var slotNumber uint64 = 1 + + addAuthorshipProof(t, babeService, slotNumber, testEpochIndex) + duration, err := time.ParseDuration("1s") + require.NoError(t, err) + + slot := Slot{ + start: time.Now(), + duration: duration, + number: slotNumber, + } + + // create babe header + babeHeader, err := builder.buildBlockBABEPrimaryPreDigest(slot) + require.NoError(t, err) + + Authorities := make([]types.Authority, 1) + Authorities[0] = types.Authority{ + Key: kp.Public().(*sr25519.PublicKey), + } + babeService.epochData.authorities = Authorities + + verifier, err := newVerifier(babeService.blockState, testEpochIndex, &verifierInfo{ + authorities: babeService.epochData.authorities, + threshold: babeService.epochData.threshold, + randomness: babeService.epochData.randomness, + }) + require.NoError(t, err) + + ok, err := verifier.verifyPrimarySlotWinner( + babeHeader.AuthorityIndex, slot.number, + babeHeader.VRFOutput, babeHeader.VRFProof) + require.NoError(t, err) + require.True(t, ok) +} + +func TestVerifyAuthorshipRight(t *testing.T) { + babeService := createTestService(t, nil) + babeService.epochData.threshold = maxThreshold + + block, _ := createTestBlock(t, babeService, genesisHeader, [][]byte{}, 1, testEpochIndex) + + verifier, err := newVerifier(babeService.blockState, testEpochIndex, &verifierInfo{ + authorities: babeService.epochData.authorities, + threshold: babeService.epochData.threshold, + randomness: babeService.epochData.randomness, + }) + require.NoError(t, err) + + err = verifier.verifyAuthorshipRight(&block.Header) + require.NoError(t, err) +} + +func TestVerifyAuthorshipRight_Equivocation(t *testing.T) { + kp, err := sr25519.GenerateKeypair() + require.NoError(t, err) + + cfg := &ServiceConfig{ + Keypair: kp, + } + + babeService := createTestService(t, cfg) + babeService.epochData.threshold = maxThreshold + + babeService.epochData.authorities = make([]types.Authority, 1) + babeService.epochData.authorities[0] = types.Authority{ + Key: kp.Public().(*sr25519.PublicKey), + } + + // create and add first block + block, _ := createTestBlock(t, babeService, genesisHeader, [][]byte{}, 1, testEpochIndex) + block.Header.Hash() + + err = babeService.blockState.AddBlock(block) + require.NoError(t, err) + + verifier, err := newVerifier(babeService.blockState, testEpochIndex, &verifierInfo{ + authorities: babeService.epochData.authorities, + threshold: babeService.epochData.threshold, + randomness: babeService.epochData.randomness, + }) + require.NoError(t, err) + + err = verifier.verifyAuthorshipRight(&block.Header) + require.NoError(t, err) + + // create new block + block2, _ := createTestBlock(t, babeService, genesisHeader, [][]byte{}, 1, testEpochIndex) + block2.Header.Hash() + + err = babeService.blockState.AddBlock(block2) + require.NoError(t, err) + + err = verifier.verifyAuthorshipRight(&block2.Header) + require.Equal(t, ErrProducerEquivocated, err) +} diff --git a/lib/babe/verify_test.go b/lib/babe/verify_test.go index d9b9a5680d..f1363a4a93 100644 --- a/lib/babe/verify_test.go +++ b/lib/babe/verify_test.go @@ -5,402 +5,1203 @@ package babe import ( "errors" + "fmt" "math/big" "testing" - "time" - "github.com/ChainSafe/gossamer/dot/state" "github.com/ChainSafe/gossamer/dot/types" - "github.com/ChainSafe/gossamer/internal/log" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/crypto/sr25519" - "github.com/ChainSafe/gossamer/lib/genesis" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func newTestVerificationManager(t *testing.T, genCfg *types.BabeConfiguration) *VerificationManager { - testDatadirPath := t.TempDir() - - ctrl := gomock.NewController(t) - telemetryMock := NewMockClient(ctrl) - telemetryMock.EXPECT().SendMessage(gomock.Any()).AnyTimes() - - config := state.Config{ - Path: testDatadirPath, - LogLevel: log.Info, - Telemetry: telemetryMock, - } - dbSrv := state.NewService(config) - dbSrv.UseMemDB() - - if genCfg == nil { - genCfg = genesisBABEConfig +func newTestHeader(t *testing.T, digest ...scale.VaryingDataTypeValue) *types.Header { + t.Helper() + header := types.NewEmptyHeader() + for _, d := range digest { + err := header.Digest.Add(d) + assert.NoError(t, err) } - gen, genTrie, genHeader := genesis.NewDevGenesisWithTrieAndHeader(t) - err := dbSrv.Initialise(gen, genHeader, genTrie) - require.NoError(t, err) - - err = dbSrv.Start() - require.NoError(t, err) - dbSrv.Epoch, err = state.NewEpochStateFromGenesis(dbSrv.DB(), dbSrv.Block, genCfg) - require.NoError(t, err) - - logger.Patch(log.SetLevel(defaultTestLogLvl)) - - vm, err := NewVerificationManager(dbSrv.Block, dbSrv.Epoch) - require.NoError(t, err) - return vm -} - -func TestVerificationManager_OnDisabled_InvalidIndex(t *testing.T) { - vm := newTestVerificationManager(t, nil) - - babeService := createTestService(t, nil) - block, _ := createTestBlock(t, babeService, genesisHeader, [][]byte{}, 1, testEpochIndex) - err := vm.SetOnDisabled(1, &block.Header) - require.Equal(t, err, ErrInvalidBlockProducerIndex) + return header } -func TestVerificationManager_OnDisabled_NewDigest(t *testing.T) { - kp, err := sr25519.GenerateKeypair() +func signAndAddSeal(t *testing.T, kp *sr25519.Keypair, header *types.Header, data []byte) { + t.Helper() + sig, err := kp.Sign(data) require.NoError(t, err) - cfg := &ServiceConfig{ - Keypair: kp, - } - - babeService := createTestService(t, cfg) - - vm := newTestVerificationManager(t, nil) - vm.epochInfo[testEpochIndex] = &verifierInfo{ - authorities: babeService.epochData.authorities, - threshold: babeService.epochData.threshold, - randomness: babeService.epochData.randomness, - } + err = header.Digest.Add(types.SealDigest{ + ConsensusEngineID: types.BabeEngineID, + Data: sig, + }) + assert.NoError(t, err) +} - parent, _ := babeService.blockState.BestBlockHeader() - block, _ := createTestBlock(t, babeService, parent, [][]byte{}, 1, testEpochIndex) - err = vm.blockState.AddBlock(block) +func newEncodedBabeDigest(t *testing.T, value scale.VaryingDataTypeValue) []byte { + t.Helper() + babeDigest := types.NewBabeDigest() + err := babeDigest.Set(value) require.NoError(t, err) - err = vm.SetOnDisabled(0, &block.Header) + enc, err := scale.Marshal(babeDigest) require.NoError(t, err) + return enc +} - // create an OnDisabled change on a different branch - block, _ = createTestBlock(t, babeService, parent, [][]byte{}, 2, testEpochIndex) - err = vm.blockState.AddBlock(block) +func encodeAndHashHeader(t *testing.T, header *types.Header) common.Hash { + t.Helper() + encHeader, err := scale.Marshal(*header) require.NoError(t, err) - err = vm.SetOnDisabled(0, &block.Header) + hash, err := common.Blake2bHash(encHeader) require.NoError(t, err) + return hash } -func TestVerificationManager_OnDisabled_DuplicateDigest(t *testing.T) { - kp, err := sr25519.GenerateKeypair() - require.NoError(t, err) - - cfg := &ServiceConfig{ - Keypair: kp, +func newTestVerifier(t *testing.T, kp *sr25519.Keypair, blockState BlockState, + threshold *scale.Uint128, secSlots bool) *verifier { + t.Helper() + authority := types.NewAuthority(kp.Public(), uint64(1)) + info := &verifierInfo{ + authorities: []types.Authority{*authority, *authority}, + randomness: Randomness{}, + threshold: threshold, + secondarySlots: secSlots, } + verifier, err := newVerifier(blockState, 1, info) + require.NoError(t, err) + return verifier +} - babeService := createTestService(t, cfg) - - vm := newTestVerificationManager(t, nil) - vm.epochInfo[testEpochIndex] = &verifierInfo{ - authorities: babeService.epochData.authorities, - threshold: babeService.epochData.threshold, - randomness: babeService.epochData.randomness, - } +func Test_getAuthorityIndex(t *testing.T) { + digest := types.NewDigest() + err := digest.Add(types.SealDigest{ + ConsensusEngineID: types.ConsensusEngineID{}, + }) + assert.NoError(t, err) + headerNoPre := types.NewEmptyHeader() + headerNoPre.Digest = digest - block, _ := createTestBlock(t, babeService, genesisHeader, [][]byte{}, 1, testEpochIndex) - err = vm.blockState.AddBlock(block) - require.NoError(t, err) + digest2 := types.NewDigest() + err = digest2.Add(types.PreRuntimeDigest{ + ConsensusEngineID: types.BabeEngineID, + Data: []byte{1}, + }) + assert.NoError(t, err) + headerInvalidPre := types.NewEmptyHeader() + headerInvalidPre.Digest = digest2 - err = vm.SetOnDisabled(0, &block.Header) - require.NoError(t, err) + // BabePrimaryPreDigest Case + babeDigest := types.NewBabeDigest() + err = babeDigest.Set(types.BabePrimaryPreDigest{AuthorityIndex: 21}) + assert.NoError(t, err) - // create an OnDisabled change on a different branch - block2, _ := createTestBlock(t, babeService, &block.Header, [][]byte{}, 2, testEpochIndex) - err = vm.blockState.AddBlock(block2) + bdEnc, err := scale.Marshal(babeDigest) require.NoError(t, err) - err = vm.SetOnDisabled(0, &block2.Header) - require.Equal(t, ErrAuthorityAlreadyDisabled, err) -} + digestPrimary := types.NewDigest() + err = digestPrimary.Add(types.PreRuntimeDigest{ + ConsensusEngineID: types.BabeEngineID, + Data: bdEnc, + }) + assert.NoError(t, err) + headerPrimary := types.NewEmptyHeader() + headerPrimary.Digest = digestPrimary -func TestVerificationManager_VerifyBlock_Ok(t *testing.T) { - babeService := createTestService(t, nil) - rt, err := babeService.blockState.GetRuntime(nil) - require.NoError(t, err) + //BabeSecondaryVRFPreDigest Case + babeDigest2 := types.NewBabeDigest() + err = babeDigest2.Set(types.BabeSecondaryVRFPreDigest{AuthorityIndex: 21}) + assert.NoError(t, err) - cfg, err := rt.BabeConfiguration() + bdEnc2, err := scale.Marshal(babeDigest2) require.NoError(t, err) - cfg.GenesisAuthorities = types.AuthoritiesToRaw(babeService.epochData.authorities) - cfg.C1 = 1 - cfg.C2 = 1 - - vm := newTestVerificationManager(t, cfg) + digestSecondary := types.NewDigest() + err = digestSecondary.Add(types.PreRuntimeDigest{ + ConsensusEngineID: types.BabeEngineID, + Data: bdEnc2, + }) + assert.NoError(t, err) + headerSecondary := types.NewEmptyHeader() + headerSecondary.Digest = digestSecondary - block, _ := createTestBlock(t, babeService, genesisHeader, [][]byte{}, 1, testEpochIndex) - err = vm.VerifyBlock(&block.Header) - require.NoError(t, err) -} + //BabeSecondaryPlainPreDigest case + babeDigest3 := types.NewBabeDigest() + err = babeDigest3.Set(types.BabeSecondaryPlainPreDigest{AuthorityIndex: 21}) + assert.NoError(t, err) -func TestVerificationManager_VerifyBlock_Secondary(t *testing.T) { - babeService := createTestService(t, nil) - rt, err := babeService.blockState.GetRuntime(nil) + bdEnc3, err := scale.Marshal(babeDigest3) require.NoError(t, err) - cfg, err := rt.BabeConfiguration() - require.NoError(t, err) + digestSecondaryPlain := types.NewDigest() + err = digestSecondaryPlain.Add(types.PreRuntimeDigest{ + ConsensusEngineID: types.BabeEngineID, + Data: bdEnc3, + }) + assert.NoError(t, err) + headerSecondaryPlain := types.NewEmptyHeader() + headerSecondaryPlain.Digest = digestSecondaryPlain - cfg.GenesisAuthorities = types.AuthoritiesToRaw(babeService.epochData.authorities) - cfg.C1 = 1 - cfg.C2 = 1 - cfg.SecondarySlots = 0 + type args struct { + header *types.Header + } + tests := []struct { + name string + args args + exp uint32 + expErr error + }{ + { + name: "No Digest", + args: args{types.NewEmptyHeader()}, + expErr: errors.New("no digest provided"), + }, + { + name: "First Digest Invalid Type", + args: args{headerNoPre}, + expErr: errors.New("first digest item is not pre-runtime digest"), + }, + { + name: "Invalid Preruntime Digest Type", + args: args{headerInvalidPre}, + expErr: errors.New("cannot decode babe header from pre-digest: EOF, field: 0"), + }, + { + name: "BabePrimaryPreDigest Type", + args: args{headerPrimary}, + exp: 21, + }, + { + name: "BabeSecondaryVRFPreDigest Type", + args: args{headerSecondary}, + exp: 21, + }, + { + name: "BabeSecondaryPlainPreDigest Type", + args: args{headerSecondaryPlain}, + exp: 21, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + res, err := getAuthorityIndex(tt.args.header) + if tt.expErr != nil { + assert.EqualError(t, err, tt.expErr.Error()) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tt.exp, res) + }) + } +} - vm := newTestVerificationManager(t, cfg) +func Test_verifier_verifyPrimarySlotWinner(t *testing.T) { + ctrl := gomock.NewController(t) + mockBlockState := NewMockBlockState(ctrl) + //Generate keys kp, err := sr25519.GenerateKeypair() - require.NoError(t, err) - - dig := createSecondaryVRFPreDigest(t, kp, 0, uint64(0), uint64(0), Randomness{}) - - bd := types.NewBabeDigest() - err = bd.Set(dig) - require.NoError(t, err) - - bdEnc, err := scale.Marshal(bd) - require.NoError(t, err) + assert.NoError(t, err) - // create pre-digest - preDigest := &types.PreRuntimeDigest{ - ConsensusEngineID: types.BabeEngineID, - Data: bdEnc, + auth := types.NewAuthority(kp.Public(), uint64(1)) + vi := &verifierInfo{ + authorities: []types.Authority{*auth}, + threshold: &scale.Uint128{}, } - // create new block header - number := big.NewInt(1) - digest := types.NewDigest() - err = digest.Add(*preDigest) - require.NoError(t, err) - - // create seal and add to digest - seal := &types.SealDigest{ - ConsensusEngineID: types.BabeEngineID, - Data: []byte{0}, + vi1 := &verifierInfo{ + authorities: []types.Authority{*auth}, + threshold: scale.MaxUint128, } - require.NoError(t, err) - err = digest.Add(*seal) - require.NoError(t, err) + v, err := newVerifier(mockBlockState, 1, vi) + assert.NoError(t, err) - header, err := types.NewHeader(common.Hash{}, common.Hash{}, common.Hash{}, number, digest) - require.NoError(t, err) + v1, err := newVerifier(mockBlockState, 1, vi1) + assert.NoError(t, err) + + output, proof, err := kp.VrfSign(makeTranscript(Randomness{}, uint64(1), 1)) + assert.NoError(t, err) - block := types.Block{ - Header: *header, - Body: nil, + type args struct { + authorityIndex uint32 + slot uint64 + vrfOutput [sr25519.VRFOutputLength]byte + vrfProof [sr25519.VRFProofLength]byte + } + tests := []struct { + name string + verifier verifier + args args + exp bool + expErr error + }{ + { + name: "Over threshold", + verifier: *v, + args: args{ + slot: 1, + vrfOutput: [32]byte{}, + vrfProof: [64]byte{}, + }, + expErr: ErrVRFOutputOverThreshold, + }, + { + name: "VRF not verified", + verifier: *v1, + args: args{ + slot: 1, + vrfOutput: [32]byte{}, + vrfProof: [64]byte{}, + }, + }, + { + name: "VRF verified", + verifier: *v1, + args: args{ + slot: 1, + vrfOutput: output, + vrfProof: proof, + }, + exp: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := &tt.verifier + res, err := b.verifyPrimarySlotWinner(tt.args.authorityIndex, tt.args.slot, tt.args.vrfOutput, tt.args.vrfProof) + if tt.expErr != nil { + assert.EqualError(t, err, tt.expErr.Error()) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tt.exp, res) + }) } - err = vm.VerifyBlock(&block.Header) - require.EqualError(t, err, "failed to verify pre-runtime digest: could not verify slot claim VRF proof") } -func TestVerificationManager_VerifyBlock_MultipleEpochs(t *testing.T) { - babeService := createTestService(t, nil) - rt, err := babeService.blockState.GetRuntime(nil) - require.NoError(t, err) +func Test_verifier_verifyPreRuntimeDigest(t *testing.T) { + ctrl := gomock.NewController(t) + mockBlockState := NewMockBlockState(ctrl) - cfg, err := rt.BabeConfiguration() - require.NoError(t, err) + //Generate keys + kp, err := sr25519.GenerateKeypair() + assert.NoError(t, err) - cfg.GenesisAuthorities = types.AuthoritiesToRaw(babeService.epochData.authorities) - cfg.C1 = 1 - cfg.C2 = 1 + //BabePrimaryPreDigest case + output, proof, err := kp.VrfSign(makeTranscript(Randomness{}, uint64(1), 1)) + assert.NoError(t, err) - vm := newTestVerificationManager(t, cfg) + secDigest1 := types.BabePrimaryPreDigest{ + SlotNumber: 1, + VRFOutput: output, + VRFProof: proof, + } + prd1, err := secDigest1.ToPreRuntimeDigest() + assert.NoError(t, err) - futureEpoch := uint64(5) + auth := types.NewAuthority(kp.Public(), uint64(1)) + vi := &verifierInfo{ + authorities: []types.Authority{*auth, *auth}, + threshold: scale.MaxUint128, + } - err = vm.epochState.(*state.EpochState).SetEpochData(futureEpoch, &types.EpochData{ - Authorities: babeService.epochData.authorities, - Randomness: babeService.epochData.randomness, - }) - require.NoError(t, err) + v, err := newVerifier(mockBlockState, 1, vi) + assert.NoError(t, err) - // create block in future epoch - block1, _ := createTestBlock(t, babeService, genesisHeader, [][]byte{}, cfg.EpochLength*futureEpoch+1, futureEpoch) - block2, _ := createTestBlock(t, babeService, &block1.Header, [][]byte{}, cfg.EpochLength*futureEpoch+2, futureEpoch) + // Invalid + v2, err := newVerifier(mockBlockState, 13, vi) + assert.NoError(t, err) - err = vm.VerifyBlock(&block2.Header) - require.NoError(t, err) + // Above threshold case + vi1 := &verifierInfo{ + authorities: []types.Authority{*auth, *auth}, + threshold: &scale.Uint128{}, + } - // create block in epoch 1 - block, _ := createTestBlock(t, babeService, genesisHeader, [][]byte{}, cfg.EpochLength-10, testEpochIndex) + v1, err := newVerifier(mockBlockState, 1, vi1) + assert.NoError(t, err) - err = vm.VerifyBlock(&block.Header) - require.NoError(t, err) -} + //BabeSecondaryVRFPreDigest case + secVRFDigest := types.BabeSecondaryVRFPreDigest{ + SlotNumber: 1, + VrfOutput: output, + VrfProof: proof, + } -func TestVerificationManager_VerifyBlock_InvalidBlockOverThreshold(t *testing.T) { - babeService := createTestService(t, nil) - rt, err := babeService.blockState.GetRuntime(nil) - require.NoError(t, err) + digestSecondaryVRF := types.NewBabeDigest() + err = digestSecondaryVRF.Set(secVRFDigest) + assert.NoError(t, err) - cfg, err := rt.BabeConfiguration() + bdEnc, err := scale.Marshal(digestSecondaryVRF) require.NoError(t, err) - cfg.GenesisAuthorities = types.AuthoritiesToRaw(babeService.epochData.authorities) - cfg.C1 = 1 - cfg.C2 = 100 + babePRD := types.NewBABEPreRuntimeDigest(bdEnc) - vm := newTestVerificationManager(t, cfg) + authVRFSec := types.NewAuthority(kp.Public(), uint64(1)) + viVRFSec := &verifierInfo{ + authorities: []types.Authority{*authVRFSec, *authVRFSec}, + threshold: scale.MaxUint128, + } - block, _ := createTestBlock(t, babeService, genesisHeader, [][]byte{}, 1, testEpochIndex) + viVRFSec2 := &verifierInfo{ + authorities: []types.Authority{*authVRFSec, *authVRFSec}, + threshold: scale.MaxUint128, + secondarySlots: true, + } - err = vm.VerifyBlock(&block.Header) - require.Equal(t, ErrVRFOutputOverThreshold, errors.Unwrap(err)) -} + vVRFSec, err := newVerifier(mockBlockState, 1, viVRFSec) + assert.NoError(t, err) -func TestVerificationManager_VerifyBlock_InvalidBlockAuthority(t *testing.T) { - babeService := createTestService(t, nil) - rt, err := babeService.blockState.GetRuntime(nil) - require.NoError(t, err) + vVRFSec2, err := newVerifier(mockBlockState, 1, viVRFSec2) + assert.NoError(t, err) - cfg, err := rt.BabeConfiguration() - require.NoError(t, err) + //BabeSecondaryPlainPreDigest case + secDigest := types.BabeSecondaryPlainPreDigest{AuthorityIndex: 0, SlotNumber: uint64(1)} + prd, err := secDigest.ToPreRuntimeDigest() + assert.NoError(t, err) - cfg.C1 = 1 - cfg.C2 = 1 - cfg.GenesisAuthorities = []types.AuthorityRaw{} + authSec := types.NewAuthority(kp.Public(), uint64(1)) + viSec := &verifierInfo{ + authorities: []types.Authority{*authSec, *authSec}, + threshold: scale.MaxUint128, + } + + viSec2 := &verifierInfo{ + authorities: []types.Authority{*authSec, *authSec}, + threshold: scale.MaxUint128, + secondarySlots: true, + } - vm := newTestVerificationManager(t, cfg) + vSec, err := newVerifier(mockBlockState, 1, viSec) + assert.NoError(t, err) - block, _ := createTestBlock(t, babeService, genesisHeader, [][]byte{}, 1, testEpochIndex) + vSec2, err := newVerifier(mockBlockState, 1, viSec2) + assert.NoError(t, err) - err = vm.VerifyBlock(&block.Header) - require.Equal(t, ErrInvalidBlockProducerIndex, errors.Unwrap(err)) + type args struct { + digest *types.PreRuntimeDigest + } + tests := []struct { + name string + verifier verifier + args args + exp scale.VaryingDataTypeValue + expErr error + }{ + { + name: "Invalid PreRuntimeDigest", + verifier: verifier{}, + args: args{&types.PreRuntimeDigest{Data: []byte{0}}}, + expErr: errors.New("unable to find VaryingDataTypeValue with index: 0"), + }, + { + name: "Invalid BlockProducer Index", + verifier: verifier{}, + args: args{prd}, + expErr: ErrInvalidBlockProducerIndex, + }, + { + name: "BabePrimaryPreDigest Case OK", + verifier: *v, + args: args{prd1}, + exp: types.BabePrimaryPreDigest{ + SlotNumber: 1, + VRFOutput: output, + VRFProof: proof, + }, + }, + { + name: "BabePrimaryPreDigest Case output over threshold", + verifier: *v1, + args: args{prd1}, + expErr: errors.New("vrf output over threshold"), + }, + { + name: "BabePrimaryPreDigest Case Invalid", + verifier: *v2, + args: args{prd1}, + expErr: ErrBadSlotClaim, + }, + { + name: "BabeSecondaryPlainPreDigest SecondarySlot false", + verifier: *vSec, + args: args{prd}, + expErr: ErrBadSlotClaim, + }, + { + name: "BabeSecondaryPlainPreDigest invalid claim", + verifier: *vSec2, + args: args{prd}, + expErr: errors.New("invalid secondary slot claim"), + }, + { + name: "BabeSecondaryVRFPreDigest SecondarySlot false", + verifier: *vVRFSec, + args: args{babePRD}, + expErr: ErrBadSlotClaim, + }, + { + name: "BabeSecondaryVRFPreDigest invalid claim", + verifier: *vVRFSec2, + args: args{babePRD}, + expErr: errors.New("invalid secondary slot claim"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := &tt.verifier + res, err := b.verifyPreRuntimeDigest(tt.args.digest) + if tt.expErr != nil { + assert.EqualError(t, err, tt.expErr.Error()) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tt.exp, res) + }) + } } -func TestVerifyPimarySlotWinner(t *testing.T) { +func Test_verifier_verifyAuthorshipRight(t *testing.T) { + ctrl := gomock.NewController(t) + mockBlockState := NewMockBlockState(ctrl) + mockBlockStateErr := NewMockBlockState(ctrl) + mockBlockStateEquiv1 := NewMockBlockState(ctrl) + mockBlockStateEquiv2 := NewMockBlockState(ctrl) + mockBlockStateEquiv3 := NewMockBlockState(ctrl) + + //Generate keys kp, err := sr25519.GenerateKeypair() - require.NoError(t, err) + assert.NoError(t, err) - cfg := &ServiceConfig{ - Keypair: kp, + // Create a VRF output and proof + output, proof, err := kp.VrfSign(makeTranscript(Randomness{}, uint64(1), 1)) + assert.NoError(t, err) + + testBabePrimaryPreDigest := types.BabePrimaryPreDigest{ + SlotNumber: 1, + VRFOutput: output, + VRFProof: proof, + } + testBabeSecondaryPlainPreDigest := types.BabeSecondaryPlainPreDigest{ + AuthorityIndex: 1, + SlotNumber: 1, + } + testBabeSecondaryVRFPreDigest := types.BabeSecondaryVRFPreDigest{ + AuthorityIndex: 1, + SlotNumber: 1, + VrfOutput: output, + VrfProof: proof, + } + testInvalidSeal := types.SealDigest{ + ConsensusEngineID: types.BabeEngineID, + Data: []byte{1}, + } + testInvalidPreRuntimeDigest := types.PreRuntimeDigest{ + ConsensusEngineID: types.BabeEngineID, + Data: []byte{1}, } - babeService := createTestService(t, cfg) + // Primary Test Header + encTestDigest := newEncodedBabeDigest(t, types.BabePrimaryPreDigest{AuthorityIndex: 0}) - // create proof that we can authorize this block - babeService.epochData.threshold = maxThreshold - babeService.epochData.authorityIndex = 0 + testDigestPrimary := types.NewDigest() + err = testDigestPrimary.Add(types.PreRuntimeDigest{ + ConsensusEngineID: types.BabeEngineID, + Data: encTestDigest, + }) + assert.NoError(t, err) + testHeaderPrimary := types.NewEmptyHeader() + testHeaderPrimary.Digest = testDigestPrimary - builder, _ := NewBlockBuilder( - babeService.keypair, - babeService.transactionState, - babeService.blockState, - babeService.slotToProof, - babeService.epochData.authorityIndex, - ) + // Secondary Plain Test Header + testParentPrd, err := testBabeSecondaryPlainPreDigest.ToPreRuntimeDigest() + assert.NoError(t, err) + testParentHeader := newTestHeader(t, *testParentPrd) - var slotNumber uint64 = 1 + testParentHash := encodeAndHashHeader(t, testParentHeader) + testSecondaryPrd, err := testBabeSecondaryPlainPreDigest.ToPreRuntimeDigest() + assert.NoError(t, err) + testSecPlainHeader := newTestHeader(t, *testSecondaryPrd) + testSecPlainHeader.ParentHash = testParentHash - addAuthorshipProof(t, babeService, slotNumber, testEpochIndex) - duration, err := time.ParseDuration("1s") - require.NoError(t, err) + // Secondary Vrf Test Header + encParentVrfDigest := newEncodedBabeDigest(t, testBabeSecondaryVRFPreDigest) + testParentVrfHeader := newTestHeader(t, *types.NewBABEPreRuntimeDigest(encParentVrfDigest)) - slot := Slot{ - start: time.Now(), - duration: duration, - number: slotNumber, + testVrfParentHash := encodeAndHashHeader(t, testParentVrfHeader) + encVrfHeader := newEncodedBabeDigest(t, testBabeSecondaryVRFPreDigest) + testSecVrfHeader := newTestHeader(t, *types.NewBABEPreRuntimeDigest(encVrfHeader)) + testSecVrfHeader.ParentHash = testVrfParentHash + + h := common.MustHexToHash("0x01") + h1 := []common.Hash{h} + + mockBlockState.EXPECT().GetAllBlocksAtDepth(gomock.Any()).Return(h1) + mockBlockState.EXPECT().GetHeader(h).Return(types.NewEmptyHeader(), nil) + + mockBlockStateErr.EXPECT().GetAllBlocksAtDepth(gomock.Any()).Return(h1) + mockBlockStateErr.EXPECT().GetHeader(h).Return(nil, errors.New("get header error")) + + mockBlockStateEquiv1.EXPECT().GetAllBlocksAtDepth(gomock.Any()).Return(h1) + mockBlockStateEquiv1.EXPECT().GetHeader(h).Return(testHeaderPrimary, nil) + + mockBlockStateEquiv2.EXPECT().GetAllBlocksAtDepth(gomock.Any()).Return(h1) + mockBlockStateEquiv2.EXPECT().GetHeader(h).Return(testSecPlainHeader, nil) + mockBlockStateEquiv3.EXPECT().GetAllBlocksAtDepth(gomock.Any()).Return(h1) + mockBlockStateEquiv3.EXPECT().GetHeader(h).Return(testSecVrfHeader, nil) + + // Case 0: First element not preruntime digest + header0 := newTestHeader(t, testInvalidSeal, testInvalidSeal) + + // Case 1: Last element not seal + header1 := newTestHeader(t, testInvalidPreRuntimeDigest, testInvalidPreRuntimeDigest) + + // Case 2: Fail to verify preruntime digest + header2 := newTestHeader(t, testInvalidPreRuntimeDigest, testInvalidSeal) + + // Case 3: Invalid Seal Length + babePrd, err := testBabePrimaryPreDigest.ToPreRuntimeDigest() + assert.NoError(t, err) + header3 := newTestHeader(t, *babePrd, testInvalidSeal) + babeVerifier := newTestVerifier(t, kp, mockBlockState, scale.MaxUint128, false) + + // Case 4: Invalid signature - BabePrimaryPreDigest + babePrd2, err := testBabePrimaryPreDigest.ToPreRuntimeDigest() + assert.NoError(t, err) + header4 := newTestHeader(t, *babePrd2) + + signAndAddSeal(t, kp, header4, []byte{1}) + babeVerifier2 := newTestVerifier(t, kp, mockBlockState, scale.MaxUint128, false) + + // Case 5: Invalid signature - BabeSecondaryPlainPreDigest + babeSecPlainPrd, err := testBabeSecondaryPlainPreDigest.ToPreRuntimeDigest() + assert.NoError(t, err) + header5 := newTestHeader(t, *babeSecPlainPrd) + + signAndAddSeal(t, kp, header5, []byte{1}) + babeVerifier3 := newTestVerifier(t, kp, mockBlockState, scale.MaxUint128, true) + + // Case 6: Invalid signature - BabeSecondaryVrfPreDigest + encSecVrfDigest := newEncodedBabeDigest(t, testBabeSecondaryVRFPreDigest) + assert.NoError(t, err) + header6 := newTestHeader(t, *types.NewBABEPreRuntimeDigest(encSecVrfDigest)) + + signAndAddSeal(t, kp, header6, []byte{1}) + babeVerifier4 := newTestVerifier(t, kp, mockBlockState, scale.MaxUint128, true) + + // Case 7: GetAuthorityIndex Err + babeParentPrd, err := testBabePrimaryPreDigest.ToPreRuntimeDigest() + assert.NoError(t, err) + babeParentHeader := newTestHeader(t, *babeParentPrd) + + parentHash := encodeAndHashHeader(t, babeParentHeader) + babePrd3, err := testBabePrimaryPreDigest.ToPreRuntimeDigest() + assert.NoError(t, err) + + header7 := newTestHeader(t, *babePrd3) + header7.ParentHash = parentHash + + hash := encodeAndHashHeader(t, header7) + signAndAddSeal(t, kp, header7, hash[:]) + babeVerifier5 := newTestVerifier(t, kp, mockBlockState, scale.MaxUint128, false) + + //// Case 8: Get header error + babeVerifier6 := newTestVerifier(t, kp, mockBlockStateErr, scale.MaxUint128, false) + + // Case 9: Equivocate case primary + babeVerifier7 := newTestVerifier(t, kp, mockBlockStateEquiv1, scale.MaxUint128, false) + + // Case 10: Equivocate case secondary plain + babeSecPlainPrd2, err := testBabeSecondaryPlainPreDigest.ToPreRuntimeDigest() + assert.NoError(t, err) + header8 := newTestHeader(t, *babeSecPlainPrd2) + + hash2 := encodeAndHashHeader(t, header8) + signAndAddSeal(t, kp, header8, hash2[:]) + babeVerifier8 := newTestVerifier(t, kp, mockBlockStateEquiv2, scale.MaxUint128, true) + + // Case 11: equivocation case secondary VRF + encVrfDigest := newEncodedBabeDigest(t, testBabeSecondaryVRFPreDigest) + assert.NoError(t, err) + header9 := newTestHeader(t, *types.NewBABEPreRuntimeDigest(encVrfDigest)) + + hash3 := encodeAndHashHeader(t, header9) + signAndAddSeal(t, kp, header9, hash3[:]) + babeVerifier9 := newTestVerifier(t, kp, mockBlockStateEquiv3, scale.MaxUint128, true) + + tests := []struct { + name string + verifier verifier + header *types.Header + expErr error + }{ + { + name: "missing digest", + verifier: verifier{}, + header: types.NewEmptyHeader(), + expErr: errors.New("block header is missing digest items"), + }, + { + name: "first digest invalid", + verifier: verifier{}, + header: header0, + expErr: errors.New("first digest item is not pre-digest"), + }, + { + name: "last digest invalid", + verifier: verifier{}, + header: header1, + expErr: errors.New("last digest item is not seal"), + }, + { + name: "invalid preruntime digest data", + verifier: verifier{}, + header: header2, + expErr: errors.New("failed to verify pre-runtime digest: EOF, field: 0"), + }, + { + name: "invalid seal length", + verifier: *babeVerifier, + header: header3, + expErr: errors.New("invalid signature length"), + }, + { + name: "invalid seal signature - primary", + verifier: *babeVerifier2, + header: header4, + expErr: ErrBadSignature, + }, + { + name: "invalid seal signature - secondary plain", + verifier: *babeVerifier3, + header: header5, + expErr: ErrBadSignature, + }, + { + name: "invalid seal signature - secondary vrf", + verifier: *babeVerifier4, + header: header6, + expErr: ErrBadSignature, + }, + { + name: "valid digest items, getAuthorityIndex error", + verifier: *babeVerifier5, + header: header7, + }, + { + name: "get header err", + verifier: *babeVerifier6, + header: header7, + }, + { + name: "equivocate - primary", + verifier: *babeVerifier7, + header: header7, + expErr: ErrProducerEquivocated, + }, + { + name: "equivocate - secondary plain", + verifier: *babeVerifier8, + header: header8, + expErr: ErrProducerEquivocated, + }, + { + name: "equivocate - secondary vrf", + verifier: *babeVerifier9, + header: header9, + expErr: ErrProducerEquivocated, + }, } - - // create babe header - babeHeader, err := builder.buildBlockBABEPrimaryPreDigest(slot) - require.NoError(t, err) - - Authorities := make([]types.Authority, 1) - Authorities[0] = types.Authority{ - Key: kp.Public().(*sr25519.PublicKey), + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := &tt.verifier + err := b.verifyAuthorshipRight(tt.header) + if tt.expErr != nil { + assert.EqualError(t, err, tt.expErr.Error()) + } else { + assert.NoError(t, err) + } + + }) } - babeService.epochData.authorities = Authorities +} - verifier, err := newVerifier(babeService.blockState, testEpochIndex, &verifierInfo{ - authorities: babeService.epochData.authorities, - threshold: babeService.epochData.threshold, - randomness: babeService.epochData.randomness, - }) - require.NoError(t, err) +func TestVerificationManager_getConfigData(t *testing.T) { + ctrl := gomock.NewController(t) + mockBlockState := NewMockBlockState(ctrl) + mockEpochStateEmpty := NewMockEpochState(ctrl) + mockEpochStateHasErr := NewMockEpochState(ctrl) + mockEpochStateGetErr := NewMockEpochState(ctrl) + + mockEpochStateEmpty.EXPECT().HasConfigData(gomock.Eq(uint64(0))).Return(false, nil) + mockEpochStateHasErr.EXPECT().HasConfigData(gomock.Eq(uint64(0))).Return(false, errNoConfigData) + mockEpochStateGetErr.EXPECT().HasConfigData(gomock.Eq(uint64(0))).Return(true, nil) + mockEpochStateGetErr.EXPECT().GetConfigData(gomock.Eq(uint64(0))).Return(nil, errNoConfigData) + + vm0, err := NewVerificationManager(mockBlockState, mockEpochStateEmpty) + assert.NoError(t, err) + vm1, err := NewVerificationManager(mockBlockState, mockEpochStateHasErr) + assert.NoError(t, err) + vm2, err := NewVerificationManager(mockBlockState, mockEpochStateGetErr) + assert.NoError(t, err) + tests := []struct { + name string + vm *VerificationManager + epoch uint64 + exp *types.ConfigData + expErr error + }{ + { + name: "cant find ConfigData", + vm: vm0, + expErr: errNoConfigData, + }, + { + name: "hasConfigData error", + vm: vm1, + expErr: errNoConfigData, + }, + { + name: "getConfigData error", + vm: vm2, + expErr: errNoConfigData, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := tt.vm + res, err := v.getConfigData(tt.epoch) + if tt.expErr != nil { + assert.EqualError(t, err, tt.expErr.Error()) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tt.exp, res) + }) + } +} - ok, err := verifier.verifyPrimarySlotWinner( - babeHeader.AuthorityIndex, slot.number, - babeHeader.VRFOutput, babeHeader.VRFProof) - require.NoError(t, err) - require.True(t, ok) +func TestVerificationManager_getVerifierInfo(t *testing.T) { + ctrl := gomock.NewController(t) + mockBlockState := NewMockBlockState(ctrl) + mockEpochStateGetErr := NewMockEpochState(ctrl) + mockEpochStateHasErr := NewMockEpochState(ctrl) + mockEpochStateThresholdErr := NewMockEpochState(ctrl) + mockEpochStateOk := NewMockEpochState(ctrl) + + mockEpochStateGetErr.EXPECT().GetEpochData(gomock.Eq(uint64(0))).Return(nil, errNoConfigData) + + mockEpochStateHasErr.EXPECT().GetEpochData(gomock.Eq(uint64(0))).Return(&types.EpochData{}, nil) + mockEpochStateHasErr.EXPECT().HasConfigData(gomock.Eq(uint64(0))).Return(false, errNoConfigData) + + mockEpochStateThresholdErr.EXPECT().GetEpochData(gomock.Eq(uint64(0))).Return(&types.EpochData{}, nil) + mockEpochStateThresholdErr.EXPECT().HasConfigData(gomock.Eq(uint64(0))).Return(true, nil) + mockEpochStateThresholdErr.EXPECT().GetConfigData(gomock.Eq(uint64(0))). + Return(&types.ConfigData{ + C1: 3, + C2: 1, + }, nil) + + mockEpochStateOk.EXPECT().GetEpochData(gomock.Eq(uint64(0))).Return(&types.EpochData{}, nil) + mockEpochStateOk.EXPECT().HasConfigData(gomock.Eq(uint64(0))).Return(true, nil) + mockEpochStateOk.EXPECT().GetConfigData(gomock.Eq(uint64(0))). + Return(&types.ConfigData{ + C1: 1, + C2: 3, + }, nil) + + vm0, err := NewVerificationManager(mockBlockState, mockEpochStateGetErr) + assert.NoError(t, err) + vm1, err := NewVerificationManager(mockBlockState, mockEpochStateHasErr) + assert.NoError(t, err) + vm2, err := NewVerificationManager(mockBlockState, mockEpochStateThresholdErr) + assert.NoError(t, err) + vm3, err := NewVerificationManager(mockBlockState, mockEpochStateOk) + assert.NoError(t, err) + + tests := []struct { + name string + vm *VerificationManager + epoch uint64 + exp *verifierInfo + expErr error + }{ + { + name: "getEpochData error", + vm: vm0, + expErr: fmt.Errorf("failed to get epoch data for epoch %d: %w", 0, errNoConfigData), + }, + { + name: "getConfigData error", + vm: vm1, + expErr: fmt.Errorf("failed to get config data: %w", errNoConfigData), + }, + { + name: "calculate threshold error", + vm: vm2, + expErr: errors.New("failed to calculate threshold: invalid C1/C2: greater than 1"), + }, + { + name: "happy path", + vm: vm3, + exp: &verifierInfo{ + threshold: scale.MaxUint128, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := tt.vm + res, err := v.getVerifierInfo(tt.epoch) + if tt.expErr != nil { + assert.EqualError(t, err, tt.expErr.Error()) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tt.exp, res) + }) + } } -func TestVerifyAuthorshipRight(t *testing.T) { - babeService := createTestService(t, nil) - babeService.epochData.threshold = maxThreshold +func TestVerificationManager_VerifyBlock(t *testing.T) { + //Generate keys + kp, err := sr25519.GenerateKeypair() + assert.NoError(t, err) - block, _ := createTestBlock(t, babeService, genesisHeader, [][]byte{}, 1, testEpochIndex) + // Create a VRF output and proof + output, proof, err := kp.VrfSign(makeTranscript(Randomness{}, uint64(1), 1)) + assert.NoError(t, err) - verifier, err := newVerifier(babeService.blockState, testEpochIndex, &verifierInfo{ - authorities: babeService.epochData.authorities, - threshold: babeService.epochData.threshold, - randomness: babeService.epochData.randomness, - }) - require.NoError(t, err) + testBlockHeaderEmpty := types.NewEmptyHeader() + testBlockHeaderEmpty.Number = big.NewInt(2) - err = verifier.verifyAuthorshipRight(&block.Header) - require.NoError(t, err) + ctrl := gomock.NewController(t) + mockBlockStateEmpty := NewMockBlockState(ctrl) + mockBlockStateCheckFinErr := NewMockBlockState(ctrl) + mockBlockStateNotFinal := NewMockBlockState(ctrl) + mockBlockStateNotFinal2 := NewMockBlockState(ctrl) + + mockEpochStateEmpty := NewMockEpochState(ctrl) + mockEpochStateSetSlotErr := NewMockEpochState(ctrl) + mockEpochStateGetEpochErr := NewMockEpochState(ctrl) + mockEpochStateSkipVerifyErr := NewMockEpochState(ctrl) + mockEpochStateSkipVerifyTrue := NewMockEpochState(ctrl) + mockEpochStateGetVerifierInfoErr := NewMockEpochState(ctrl) + mockEpochStateNilBlockStateErr := NewMockEpochState(ctrl) + mockEpochStateVerifyAuthorshipErr := NewMockEpochState(ctrl) + + mockBlockStateCheckFinErr.EXPECT().NumberIsFinalised(gomock.Eq(big.NewInt(1))).Return(false, errFailedFinalisation) + + mockBlockStateNotFinal.EXPECT().NumberIsFinalised(gomock.Eq(big.NewInt(1))).Return(false, nil) + + mockBlockStateNotFinal2.EXPECT().NumberIsFinalised(gomock.Eq(big.NewInt(1))).Return(false, nil) + mockEpochStateSetSlotErr.EXPECT().SetFirstSlot(gomock.Eq(uint64(1))).Return(errSetFirstSlot) + + mockEpochStateGetEpochErr.EXPECT().GetEpochForBlock(gomock.Eq(testBlockHeaderEmpty)). + Return(uint64(0), errGetEpoch) + + mockEpochStateSkipVerifyErr.EXPECT().GetEpochForBlock(gomock.Eq(testBlockHeaderEmpty)).Return(uint64(1), nil) + mockEpochStateSkipVerifyErr.EXPECT().GetEpochData(gomock.Eq(uint64(1))).Return(nil, errGetEpochData) + mockEpochStateSkipVerifyErr.EXPECT().SkipVerify(gomock.Eq(testBlockHeaderEmpty)).Return(false, errSkipVerify) + + mockEpochStateSkipVerifyTrue.EXPECT().GetEpochForBlock(gomock.Eq(testBlockHeaderEmpty)).Return(uint64(1), nil) + mockEpochStateSkipVerifyTrue.EXPECT().GetEpochData(gomock.Eq(uint64(1))).Return(nil, errGetEpochData) + mockEpochStateSkipVerifyTrue.EXPECT().SkipVerify(gomock.Eq(testBlockHeaderEmpty)).Return(true, nil) + + mockEpochStateGetVerifierInfoErr.EXPECT().GetEpochForBlock(gomock.Eq(testBlockHeaderEmpty)).Return(uint64(1), nil) + mockEpochStateGetVerifierInfoErr.EXPECT().GetEpochData(gomock.Eq(uint64(1))). + Return(nil, errGetEpochData) + mockEpochStateGetVerifierInfoErr.EXPECT().SkipVerify(gomock.Eq(testBlockHeaderEmpty)).Return(false, nil) + + mockEpochStateNilBlockStateErr.EXPECT().GetEpochForBlock(gomock.Eq(testBlockHeaderEmpty)).Return(uint64(1), nil) + mockEpochStateVerifyAuthorshipErr.EXPECT().GetEpochForBlock(gomock.Eq(testBlockHeaderEmpty)).Return(uint64(1), nil) + + block1Header := types.NewEmptyHeader() + block1Header.Number = big.NewInt(1) + + testBabeSecondaryVRFPreDigest := types.BabeSecondaryVRFPreDigest{ + AuthorityIndex: 1, + SlotNumber: uint64(1), + VrfOutput: output, + VrfProof: proof, + } + encVrfDigest := newEncodedBabeDigest(t, testBabeSecondaryVRFPreDigest) + assert.NoError(t, err) + block1Header2 := newTestHeader(t, *types.NewBABEPreRuntimeDigest(encVrfDigest)) + block1Header2.Number = big.NewInt(1) + + authority := types.NewAuthority(kp.Public(), uint64(1)) + info := &verifierInfo{ + authorities: []types.Authority{*authority, *authority}, + threshold: scale.MaxUint128, + secondarySlots: true, + } + + vm0, err := NewVerificationManager(mockBlockStateCheckFinErr, mockEpochStateEmpty) + assert.NoError(t, err) + vm1, err := NewVerificationManager(mockBlockStateNotFinal, mockEpochStateEmpty) + assert.NoError(t, err) + vm2, err := NewVerificationManager(mockBlockStateNotFinal2, mockEpochStateSetSlotErr) + assert.NoError(t, err) + vm3, err := NewVerificationManager(mockBlockStateNotFinal2, mockEpochStateGetEpochErr) + assert.NoError(t, err) + vm4, err := NewVerificationManager(mockBlockStateEmpty, mockEpochStateSkipVerifyErr) + assert.NoError(t, err) + vm5, err := NewVerificationManager(mockBlockStateEmpty, mockEpochStateSkipVerifyTrue) + assert.NoError(t, err) + vm6, err := NewVerificationManager(mockBlockStateEmpty, mockEpochStateGetVerifierInfoErr) + assert.NoError(t, err) + vm7 := &VerificationManager{ + epochState: mockEpochStateNilBlockStateErr, + epochInfo: make(map[uint64]*verifierInfo), + onDisabled: make(map[uint64]map[uint32][]*onDisabledInfo), + } + vm8, err := NewVerificationManager(mockBlockStateEmpty, mockEpochStateVerifyAuthorshipErr) + assert.NoError(t, err) + + vm7.epochInfo[1] = info + vm8.epochInfo[1] = info + + tests := []struct { + name string + vm *VerificationManager + header *types.Header + expErr error + }{ + { + name: "fail to check block 1 finalisation", + vm: vm0, + header: block1Header, + expErr: fmt.Errorf("failed to check if block 1 is finalised: %w", errFailedFinalisation), + }, + { + name: "get slot from header error", + vm: vm1, + header: block1Header, + expErr: fmt.Errorf("failed to get slot from block 1: %w", errMissingDigest), + }, + { + name: "set first slot error", + vm: vm2, + header: block1Header2, + expErr: fmt.Errorf("failed to set current epoch after receiving block 1: %w", errSetFirstSlot), + }, + { + name: "get epoch error", + vm: vm3, + header: testBlockHeaderEmpty, + expErr: fmt.Errorf("failed to get epoch for block header: %w", errGetEpoch), + }, + { + name: "skip verify err", + vm: vm4, + header: testBlockHeaderEmpty, + expErr: fmt.Errorf("failed to check if verification can be skipped: %w", errSkipVerify), + }, + { + name: "skip verify true", + vm: vm5, + header: testBlockHeaderEmpty, + }, + { + name: "get verifierInfo err", + vm: vm6, + header: testBlockHeaderEmpty, + expErr: fmt.Errorf("failed to get verifier info for block 2: "+ + "failed to get epoch data for epoch 1: %w", errGetEpochData), + }, + { + name: "nil blockState error", + vm: vm7, + header: testBlockHeaderEmpty, + expErr: fmt.Errorf("failed to create new BABE verifier: %w", errNilBlockState), + }, + { + name: "verify block authorship err", + vm: vm8, + header: testBlockHeaderEmpty, + expErr: errMissingDigestItems, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := tt.vm + err := v.VerifyBlock(tt.header) + if tt.expErr != nil { + assert.EqualError(t, err, tt.expErr.Error()) + } else { + assert.NoError(t, err) + } + }) + } } -func TestVerifyAuthorshipRight_Equivocation(t *testing.T) { +func TestVerificationManager_SetOnDisabled(t *testing.T) { + //Generate keys kp, err := sr25519.GenerateKeypair() - require.NoError(t, err) + assert.NoError(t, err) - cfg := &ServiceConfig{ - Keypair: kp, - } + testHeader := types.NewEmptyHeader() + testHeader.Number = big.NewInt(2) - babeService := createTestService(t, cfg) - babeService.epochData.threshold = maxThreshold + ctrl := gomock.NewController(t) + mockBlockStateEmpty := NewMockBlockState(ctrl) + mockBlockStateIsDescendantErr := NewMockBlockState(ctrl) + mockBlockStateAuthorityDisabled := NewMockBlockState(ctrl) + mockBlockStateOk := NewMockBlockState(ctrl) - babeService.epochData.authorities = make([]types.Authority, 1) - babeService.epochData.authorities[0] = types.Authority{ - Key: kp.Public().(*sr25519.PublicKey), - } + mockEpochStateGetEpochErr := NewMockEpochState(ctrl) + mockEpochStateGetEpochDataErr := NewMockEpochState(ctrl) + mockEpochStateIndexLenErr := NewMockEpochState(ctrl) + mockEpochStateSetDisabledProd := NewMockEpochState(ctrl) + mockEpochStateOk := NewMockEpochState(ctrl) + mockEpochStateOk2 := NewMockEpochState(ctrl) + mockEpochStateOk3 := NewMockEpochState(ctrl) - // create and add first block - block, _ := createTestBlock(t, babeService, genesisHeader, [][]byte{}, 1, testEpochIndex) - block.Header.Hash() + mockEpochStateGetEpochErr.EXPECT().GetEpochForBlock(gomock.Eq(types.NewEmptyHeader())).Return(uint64(0), errGetEpoch) - err = babeService.blockState.AddBlock(block) - require.NoError(t, err) + mockEpochStateGetEpochDataErr.EXPECT().GetEpochForBlock(gomock.Eq(types.NewEmptyHeader())).Return(uint64(0), nil) + mockEpochStateGetEpochDataErr.EXPECT().GetEpochData(gomock.Eq(uint64(0))).Return(nil, errGetEpochData) - verifier, err := newVerifier(babeService.blockState, testEpochIndex, &verifierInfo{ - authorities: babeService.epochData.authorities, - threshold: babeService.epochData.threshold, - randomness: babeService.epochData.randomness, - }) - require.NoError(t, err) + mockEpochStateIndexLenErr.EXPECT().GetEpochForBlock(gomock.Eq(types.NewEmptyHeader())).Return(uint64(2), nil) - err = verifier.verifyAuthorshipRight(&block.Header) - require.NoError(t, err) + mockEpochStateSetDisabledProd.EXPECT().GetEpochForBlock(gomock.Eq(types.NewEmptyHeader())).Return(uint64(2), nil) - // create new block - block2, _ := createTestBlock(t, babeService, genesisHeader, [][]byte{}, 1, testEpochIndex) - block2.Header.Hash() + mockEpochStateOk.EXPECT().GetEpochForBlock(gomock.Eq(types.NewEmptyHeader())).Return(uint64(2), nil) + mockBlockStateIsDescendantErr.EXPECT().IsDescendantOf(gomock.Any(), gomock.Any()).Return(false, errDescendant) - err = babeService.blockState.AddBlock(block2) - require.NoError(t, err) + mockEpochStateOk2.EXPECT().GetEpochForBlock(gomock.Eq(testHeader)).Return(uint64(2), nil) + mockBlockStateAuthorityDisabled.EXPECT().IsDescendantOf(gomock.Any(), gomock.Any()).Return(true, nil) + + mockEpochStateOk3.EXPECT().GetEpochForBlock(gomock.Eq(testHeader)).Return(uint64(2), nil) + mockBlockStateOk.EXPECT().IsDescendantOf(gomock.Any(), gomock.Any()).Return(false, nil) - err = verifier.verifyAuthorshipRight(&block2.Header) - require.Equal(t, ErrProducerEquivocated, err) + authority := types.NewAuthority(kp.Public(), uint64(1)) + info := &verifierInfo{ + authorities: []types.Authority{*authority, *authority}, + threshold: scale.MaxUint128, + secondarySlots: true, + } + + disabledInfo := []*onDisabledInfo{ + { + blockNumber: big.NewInt(2), + }, + } + + vm0, err := NewVerificationManager(mockBlockStateEmpty, mockEpochStateGetEpochErr) + assert.NoError(t, err) + + vm1, err := NewVerificationManager(mockBlockStateEmpty, mockEpochStateGetEpochDataErr) + assert.NoError(t, err) + vm1.epochInfo[1] = info + + vm2, err := NewVerificationManager(mockBlockStateEmpty, mockEpochStateIndexLenErr) + assert.NoError(t, err) + vm2.epochInfo[2] = info + + vm3, err := NewVerificationManager(mockBlockStateEmpty, mockEpochStateSetDisabledProd) + assert.NoError(t, err) + vm3.epochInfo[2] = info + + vm4, err := NewVerificationManager(mockBlockStateIsDescendantErr, mockEpochStateOk) + assert.NoError(t, err) + vm4.epochInfo[2] = info + vm4.onDisabled[2] = map[uint32][]*onDisabledInfo{} + vm4.onDisabled[2][0] = disabledInfo + + vm5, err := NewVerificationManager(mockBlockStateAuthorityDisabled, mockEpochStateOk2) + assert.NoError(t, err) + vm5.epochInfo[2] = info + vm5.onDisabled[2] = map[uint32][]*onDisabledInfo{} + vm5.onDisabled[2][0] = disabledInfo + + vm6, err := NewVerificationManager(mockBlockStateOk, mockEpochStateOk3) + assert.NoError(t, err) + vm6.epochInfo[2] = info + vm6.onDisabled[2] = map[uint32][]*onDisabledInfo{} + vm6.onDisabled[2][0] = disabledInfo + + type args struct { + index uint32 + header *types.Header + } + tests := []struct { + name string + vm *VerificationManager + args args + expErr error + }{ + { + name: "get epoch err", + vm: vm0, + args: args{ + header: types.NewEmptyHeader(), + }, + expErr: errGetEpoch, + }, + { + name: "get epoch data err", + vm: vm1, + args: args{ + header: types.NewEmptyHeader(), + }, + expErr: fmt.Errorf("failed to get epoch data for epoch %d: %w", 0, errGetEpochData), + }, + { + name: "index length error", + vm: vm2, + args: args{ + index: 10000, + header: types.NewEmptyHeader(), + }, + expErr: ErrInvalidBlockProducerIndex, + }, + { + name: "set disabled producers", + vm: vm3, + args: args{ + header: types.NewEmptyHeader(), + }, + }, + { + name: "is Descendant of err", + vm: vm4, + args: args{ + header: types.NewEmptyHeader(), + }, + expErr: errDescendant, + }, + { + name: "authority already disabled", + vm: vm5, + args: args{ + header: testHeader, + }, + expErr: ErrAuthorityAlreadyDisabled, + }, + { + name: "happy path", + vm: vm6, + args: args{ + header: testHeader, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := tt.vm + err := v.SetOnDisabled(tt.args.index, tt.args.header) + if tt.expErr != nil { + assert.EqualError(t, err, tt.expErr.Error()) + } else { + assert.NoError(t, err) + } + }) + } }