diff --git a/dot/core/errors.go b/dot/core/errors.go index 76471f196d..10364b8499 100644 --- a/dot/core/errors.go +++ b/dot/core/errors.go @@ -44,9 +44,6 @@ var ( // ErrEmptyRuntimeCode is returned when the storage :code is empty ErrEmptyRuntimeCode = errors.New("new :code is empty") - // ErrNilDigestHandler is returned when the DigestHandler interface is nil - ErrNilDigestHandler = errors.New("cannot have nil DigestHandler") - errNilCodeSubstitutedState = errors.New("cannot have nil CodeSubstitutedStat") ) diff --git a/dot/core/helpers_test.go b/dot/core/helpers_test.go index 3a2fdcdcbf..48a2f82499 100644 --- a/dot/core/helpers_test.go +++ b/dot/core/helpers_test.go @@ -7,7 +7,6 @@ import ( "path/filepath" "testing" - "github.com/ChainSafe/gossamer/dot/digest" "github.com/ChainSafe/gossamer/dot/network" "github.com/ChainSafe/gossamer/dot/state" "github.com/ChainSafe/gossamer/internal/log" @@ -32,10 +31,6 @@ func NewTestService(t *testing.T, cfg *Config) *Service { cfg = &Config{} } - if cfg.DigestHandler == nil { - cfg.DigestHandler = &digest.Handler{} // only for nil check in NewService - } - if cfg.Keystore == nil { cfg.Keystore = keystore.NewGlobalKeystore() kp, err := sr25519.GenerateKeypair() diff --git a/dot/core/interface.go b/dot/core/interface.go index 54d5cc46b1..aadf9b908a 100644 --- a/dot/core/interface.go +++ b/dot/core/interface.go @@ -17,7 +17,7 @@ import ( "github.com/ChainSafe/gossamer/lib/transaction" ) -//go:generate mockgen -destination=mock_core_test.go -package $GOPACKAGE . BlockState,StorageState,TransactionState,Network,EpochState,CodeSubstitutedState,DigestHandler +//go:generate mockgen -destination=mock_core_test.go -package $GOPACKAGE . BlockState,StorageState,TransactionState,Network,EpochState,CodeSubstitutedState // BlockState interface for block state methods type BlockState interface { @@ -87,8 +87,3 @@ type CodeSubstitutedState interface { LoadCodeSubstitutedBlockHash() common.Hash StoreCodeSubstitutedBlockHash(hash common.Hash) error } - -// DigestHandler is the interface for the consensus digest handler -type DigestHandler interface { - HandleDigests(header *types.Header) -} diff --git a/dot/core/messages_integration_test.go b/dot/core/messages_integration_test.go index d38ed2fe3f..c20d6453be 100644 --- a/dot/core/messages_integration_test.go +++ b/dot/core/messages_integration_test.go @@ -78,10 +78,6 @@ func TestService_HandleBlockProduced(t *testing.T) { Keystore: keystore.NewGlobalKeystore(), } - digestHandler := NewMockDigestHandler(ctrl) - digestHandler.EXPECT().HandleDigests(gomock.AssignableToTypeOf(new(types.Header))) - cfg.DigestHandler = digestHandler - s := NewTestService(t, cfg) err := s.Start() require.NoError(t, err) @@ -137,9 +133,6 @@ func TestService_HandleTransactionMessage(t *testing.T) { telemetryMock := NewMockClient(ctrl) telemetryMock.EXPECT().SendMessage(gomock.Any()).AnyTimes() - digestHandler := NewMockDigestHandler(ctrl) - digestHandler.EXPECT().HandleDigests(gomock.AssignableToTypeOf(new(types.Header))) - net := NewMockNetwork(ctrl) net.EXPECT().GossipMessage(gomock.AssignableToTypeOf(new(network.TransactionMessage))).AnyTimes() net.EXPECT().IsSynced().Return(true).AnyTimes() @@ -151,7 +144,6 @@ func TestService_HandleTransactionMessage(t *testing.T) { cfg := &Config{ Keystore: ks, TransactionState: state.NewTransactionState(telemetryMock), - DigestHandler: digestHandler, Network: net, } diff --git a/dot/core/mock_core_test.go b/dot/core/mock_core_test.go index 7cfa4e471a..ee0d44a027 100644 --- a/dot/core/mock_core_test.go +++ b/dot/core/mock_core_test.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ChainSafe/gossamer/dot/core (interfaces: BlockState,StorageState,TransactionState,Network,EpochState,CodeSubstitutedState,DigestHandler) +// Source: github.com/ChainSafe/gossamer/dot/core (interfaces: BlockState,StorageState,TransactionState,Network,EpochState,CodeSubstitutedState) // Package core is a generated GoMock package. package core @@ -803,38 +803,3 @@ func (mr *MockCodeSubstitutedStateMockRecorder) StoreCodeSubstitutedBlockHash(ar mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StoreCodeSubstitutedBlockHash", reflect.TypeOf((*MockCodeSubstitutedState)(nil).StoreCodeSubstitutedBlockHash), 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) -} diff --git a/dot/core/service.go b/dot/core/service.go index e267b5a051..bd41a71af5 100644 --- a/dot/core/service.go +++ b/dot/core/service.go @@ -50,7 +50,6 @@ type Service struct { storageState StorageState transactionState TransactionState net Network - digestHandler DigestHandler // map of code substitutions keyed by block hash codeSubstitute map[common.Hash]string @@ -71,7 +70,6 @@ type Config struct { Network Network Keystore *keystore.GlobalKeystore Runtime runtime.Instance - DigestHandler DigestHandler CodeSubstitutes map[common.Hash]string CodeSubstitutedState CodeSubstitutedState @@ -96,10 +94,6 @@ func NewService(cfg *Config) (*Service, error) { return nil, ErrNilNetwork } - if cfg.DigestHandler == nil { - return nil, ErrNilDigestHandler - } - if cfg.CodeSubstitutedState == nil { return nil, errNilCodeSubstitutedState } @@ -121,7 +115,6 @@ func NewService(cfg *Config) (*Service, error) { blockAddCh: blockAddCh, codeSubstitute: cfg.CodeSubstitutes, codeSubstitutedState: cfg.CodeSubstitutedState, - digestHandler: cfg.DigestHandler, } return srv, nil @@ -218,9 +211,6 @@ func (s *Service) handleBlock(block *types.Block, state *rtstorage.TrieState) er logger.Debugf("imported block %s and stored state trie with root %s", block.Header.Hash(), state.MustRoot()) - // handle consensus digests - s.digestHandler.HandleDigests(&block.Header) - rt, err := s.blockState.GetRuntime(&block.Header.ParentHash) if err != nil { return err diff --git a/dot/core/service_integration_test.go b/dot/core/service_integration_test.go index d6f860f2a1..a1f76705bb 100644 --- a/dot/core/service_integration_test.go +++ b/dot/core/service_integration_test.go @@ -167,10 +167,6 @@ func TestAnnounceBlock(t *testing.T) { Network: net, } - digestHandler := NewMockDigestHandler(ctrl) - digestHandler.EXPECT().HandleDigests(gomock.AssignableToTypeOf(new(types.Header))) - cfg.DigestHandler = digestHandler - s := NewTestService(t, cfg) err := s.Start() require.NoError(t, err) @@ -583,9 +579,6 @@ func TestService_GetRuntimeVersion(t *testing.T) { func TestService_HandleSubmittedExtrinsic(t *testing.T) { cfg := &Config{} ctrl := gomock.NewController(t) - digestHandler := NewMockDigestHandler(ctrl) - digestHandler.EXPECT().HandleDigests(gomock.AssignableToTypeOf(new(types.Header))) - cfg.DigestHandler = digestHandler net := NewMockNetwork(ctrl) net.EXPECT().GossipMessage(gomock.AssignableToTypeOf(new(network.TransactionMessage))) diff --git a/dot/core/service_test.go b/dot/core/service_test.go index 47eeae82f1..3a178bfc83 100644 --- a/dot/core/service_test.go +++ b/dot/core/service_test.go @@ -273,6 +273,7 @@ func Test_Service_handleCodeSubstitution(t *testing.T) { func Test_Service_handleBlock(t *testing.T) { t.Parallel() + execTest := func(t *testing.T, s *Service, block *types.Block, trieState *rtstorage.TrieState, expErr error) { err := s.handleBlock(block, trieState) assert.ErrorIs(t, err, expErr) @@ -280,6 +281,7 @@ func Test_Service_handleBlock(t *testing.T) { assert.EqualError(t, err, expErr.Error()) } } + t.Run("nil input", func(t *testing.T) { t.Parallel() service := &Service{} @@ -366,13 +368,10 @@ func Test_Service_handleBlock(t *testing.T) { mockBlockState := NewMockBlockState(ctrl) mockBlockState.EXPECT().AddBlock(&block).Return(blocktree.ErrBlockExists) mockBlockState.EXPECT().GetRuntime(&block.Header.ParentHash).Return(nil, errTestDummyError) - mockDigestHandler := NewMockDigestHandler(ctrl) - mockDigestHandler.EXPECT().HandleDigests(&block.Header) service := &Service{ - storageState: mockStorageState, - blockState: mockBlockState, - digestHandler: mockDigestHandler, + storageState: mockStorageState, + blockState: mockBlockState, } execTest(t, service, &block, trieState, errTestDummyError) }) @@ -396,13 +395,10 @@ func Test_Service_handleBlock(t *testing.T) { mockBlockState.EXPECT().GetRuntime(&block.Header.ParentHash).Return(runtimeMock, nil) mockBlockState.EXPECT().HandleRuntimeChanges(trieState, runtimeMock, block.Header.Hash()). Return(errTestDummyError) - mockDigestHandler := NewMockDigestHandler(ctrl) - mockDigestHandler.EXPECT().HandleDigests(&block.Header) service := &Service{ - storageState: mockStorageState, - blockState: mockBlockState, - digestHandler: mockDigestHandler, + storageState: mockStorageState, + blockState: mockBlockState, } execTest(t, service, &block, trieState, errTestDummyError) }) @@ -425,14 +421,11 @@ func Test_Service_handleBlock(t *testing.T) { mockBlockState.EXPECT().AddBlock(&block).Return(blocktree.ErrBlockExists) mockBlockState.EXPECT().GetRuntime(&block.Header.ParentHash).Return(runtimeMock, nil) mockBlockState.EXPECT().HandleRuntimeChanges(trieState, runtimeMock, block.Header.Hash()).Return(nil) - mockDigestHandler := NewMockDigestHandler(ctrl) - mockDigestHandler.EXPECT().HandleDigests(&block.Header) service := &Service{ - storageState: mockStorageState, - blockState: mockBlockState, - digestHandler: mockDigestHandler, - ctx: context.Background(), + storageState: mockStorageState, + blockState: mockBlockState, + ctx: context.Background(), } execTest(t, service, &block, trieState, nil) }) @@ -488,17 +481,14 @@ func Test_Service_HandleBlockProduced(t *testing.T) { mockBlockState.EXPECT().AddBlock(&block).Return(blocktree.ErrBlockExists) mockBlockState.EXPECT().GetRuntime(&block.Header.ParentHash).Return(runtimeMock, nil) mockBlockState.EXPECT().HandleRuntimeChanges(trieState, runtimeMock, block.Header.Hash()).Return(nil) - mockDigestHandler := NewMockDigestHandler(ctrl) - mockDigestHandler.EXPECT().HandleDigests(&block.Header) mockNetwork := NewMockNetwork(ctrl) mockNetwork.EXPECT().GossipMessage(msg) service := &Service{ - storageState: mockStorageState, - blockState: mockBlockState, - digestHandler: mockDigestHandler, - net: mockNetwork, - ctx: context.Background(), + storageState: mockStorageState, + blockState: mockBlockState, + net: mockNetwork, + ctx: context.Background(), } execTest(t, service, &block, trieState, nil) }) diff --git a/dot/digest/digest.go b/dot/digest/digest.go index 1e8794a34c..ad17d166d9 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -6,7 +6,9 @@ package digest import ( "context" "errors" + "fmt" + "github.com/ChainSafe/gossamer/dot/state" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/internal/log" "github.com/ChainSafe/gossamer/lib/services" @@ -15,6 +17,8 @@ import ( var ( _ services.Service = &Handler{} + + ErrDefineNextEpoch = errors.New("cannot define next epoch data and config") ) // Handler is used to handle consensus messages and relevant authority updates to BABE and GRANDPA @@ -118,12 +122,14 @@ func (h *Handler) NextGrandpaAuthorityChange() (next uint) { func (h *Handler) HandleDigests(header *types.Header) { for i, d := range header.Digest.Types { val, ok := d.Value().(types.ConsensusDigest) - if ok { - err := h.handleConsensusDigest(&val, header) - if err != nil { - h.logger.Errorf("cannot handle digests for block number %d, index %d, digest %s: %s", - header.Number, i, d.Value(), err) - } + if !ok { + continue + } + + err := h.handleConsensusDigest(&val, header) + if err != nil { + h.logger.Errorf("cannot handle digest for block number %d, index %d, digest %s: %s", + header.Number, i, d.Value(), err) } } } @@ -175,15 +181,35 @@ func (h *Handler) handleGrandpaConsensusDigest(digest scale.VaryingDataType, hea } func (h *Handler) handleBabeConsensusDigest(digest scale.VaryingDataType, header *types.Header) error { + headerHash := header.Hash() + switch val := digest.Value().(type) { case types.NextEpochData: - h.logger.Debugf("handling BABENextEpochData data: %v", digest) - return h.handleNextEpochData(val, header) + currEpoch, err := h.epochState.GetEpochForBlock(header) + if err != nil { + return fmt.Errorf("cannot get epoch for block %d (%s): %w", + header.Number, headerHash, err) + } + + nextEpoch := currEpoch + 1 + h.epochState.StoreBABENextEpochData(nextEpoch, headerHash, val) + h.logger.Debugf("stored BABENextEpochData data: %v for hash: %s to epoch: %d", digest, headerHash, nextEpoch) + return nil + case types.BABEOnDisabled: return h.handleBABEOnDisabled(val, header) + case types.NextConfigData: - h.logger.Debugf("handling BABENextConfigData data: %v", digest) - return h.handleNextConfigData(val, header) + currEpoch, err := h.epochState.GetEpochForBlock(header) + if err != nil { + return fmt.Errorf("cannot get epoch for block %d (%s): %w", + header.Number, headerHash, err) + } + + nextEpoch := currEpoch + 1 + h.epochState.StoreBABENextConfigData(nextEpoch, headerHash, val) + h.logger.Debugf("stored BABENextConfigData data: %v for hash: %s to epoch: %d", digest, headerHash, nextEpoch) + return nil } return errors.New("invalid consensus digest data") @@ -197,6 +223,7 @@ func (h *Handler) handleBlockImport(ctx context.Context) { continue } + h.HandleDigests(&block.Header) err := h.handleGrandpaChangesOnImport(block.Header.Number) if err != nil { h.logger.Errorf("failed to handle grandpa changes on block import: %s", err) @@ -215,7 +242,12 @@ func (h *Handler) handleBlockFinalisation(ctx context.Context) { continue } - err := h.handleGrandpaChangesOnFinalization(info.Header.Number) + err := h.persistBABEDigestsForNextEpoch(&info.Header) + if err != nil { + h.logger.Errorf("failed to store babe next epoch digest: %s", err) + } + + err = h.handleGrandpaChangesOnFinalization(info.Header.Number) if err != nil { h.logger.Errorf("failed to handle grandpa changes on block finalisation: %s", err) } @@ -225,6 +257,35 @@ func (h *Handler) handleBlockFinalisation(ctx context.Context) { } } +// persistBABEDigestsForNextEpoch is called only when a block is finalised +// and defines the correct next epoch data and next config data. +func (h *Handler) persistBABEDigestsForNextEpoch(finalizedHeader *types.Header) error { + currEpoch, err := h.epochState.GetEpochForBlock(finalizedHeader) + if err != nil { + return fmt.Errorf("cannot get epoch for block %d (%s): %w", + finalizedHeader.Number, finalizedHeader.Hash(), err) + } + + nextEpoch := currEpoch + 1 + err = h.epochState.FinalizeBABENextEpochData(nextEpoch) + if err != nil && !errors.Is(err, state.ErrEpochNotInMemory) { + return fmt.Errorf("cannot finalize babe next epoch data for block number %d (%s): %w", + finalizedHeader.Number, finalizedHeader.Hash(), err) + } + + err = h.epochState.FinalizeBABENextConfigData(nextEpoch) + if err == nil { + return nil + } else if errors.Is(err, state.ErrEpochNotInMemory) { + return fmt.Errorf("%w: %s", ErrDefineNextEpoch, err) + } + + // the epoch state does not contains any information about the next epoch + return fmt.Errorf("cannot finalize babe next config data for block number %d (%s): %w", + finalizedHeader.Number, finalizedHeader.Hash(), err) + +} + func (h *Handler) handleGrandpaChangesOnImport(num uint) error { resume := h.grandpaResume if resume != nil && num >= resume.atBlock { @@ -233,17 +294,12 @@ func (h *Handler) handleGrandpaChangesOnImport(num uint) error { fc := h.grandpaForcedChange if fc != nil && num >= fc.atBlock { - err := h.grandpaState.IncrementSetID() + curr, err := h.grandpaState.IncrementSetID() if err != nil { return err } h.grandpaForcedChange = nil - curr, err := h.grandpaState.GetCurrentSetID() - if err != nil { - return err - } - h.logger.Debugf("incremented grandpa set id %d", curr) } @@ -258,17 +314,12 @@ func (h *Handler) handleGrandpaChangesOnFinalization(num uint) error { sc := h.grandpaScheduledChange if sc != nil && num >= sc.atBlock { - err := h.grandpaState.IncrementSetID() + curr, err := h.grandpaState.IncrementSetID() if err != nil { return err } h.grandpaScheduledChange = nil - curr, err := h.grandpaState.GetCurrentSetID() - if err != nil { - return err - } - h.logger.Debugf("incremented grandpa set id %d", curr) } @@ -381,32 +432,3 @@ func (h *Handler) handleBABEOnDisabled(_ types.BABEOnDisabled, _ *types.Header) h.logger.Debug("handling BABEOnDisabled") return nil } - -func (h *Handler) handleNextEpochData(act types.NextEpochData, header *types.Header) error { - currEpoch, err := h.epochState.GetEpochForBlock(header) - if err != nil { - return err - } - - // set EpochState epoch data for upcoming epoch - data, err := act.ToEpochData() - if err != nil { - return err - } - - h.logger.Debugf("setting data for block number %d and epoch %d with data: %v", - header.Number, currEpoch+1, data) - return h.epochState.SetEpochData(currEpoch+1, data) -} - -func (h *Handler) handleNextConfigData(config types.NextConfigData, header *types.Header) error { - currEpoch, err := h.epochState.GetEpochForBlock(header) - if err != nil { - return err - } - - h.logger.Debugf("setting BABE config data for block number %d and epoch %d with data: %v", - header.Number, currEpoch+1, config.ToConfigData()) - // set EpochState config data for upcoming epoch - return h.epochState.SetConfigData(currEpoch+1, config.ToConfigData()) -} diff --git a/dot/digest/digest_test.go b/dot/digest/digest_test.go index 7d15248d6e..dcb0d8633b 100644 --- a/dot/digest/digest_test.go +++ b/dot/digest/digest_test.go @@ -7,6 +7,8 @@ import ( "testing" "time" + "context" + "github.com/ChainSafe/gossamer/dot/state" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/internal/log" @@ -23,7 +25,7 @@ import ( //go:generate mockgen -destination=mock_telemetry_test.go -package $GOPACKAGE github.com/ChainSafe/gossamer/dot/telemetry Client -func newTestHandler(t *testing.T) *Handler { +func newTestHandler(t *testing.T) (*Handler, *state.Service) { testDatadirPath := t.TempDir() ctrl := gomock.NewController(t) @@ -49,11 +51,11 @@ func newTestHandler(t *testing.T) *Handler { dh, err := NewHandler(log.Critical, stateSrvc.Block, stateSrvc.Epoch, stateSrvc.Grandpa) require.NoError(t, err) - return dh + return dh, stateSrvc } func TestHandler_GrandpaScheduledChange(t *testing.T) { - handler := newTestHandler(t) + handler, _ := newTestHandler(t) handler.Start() defer handler.Stop() @@ -112,7 +114,7 @@ func TestHandler_GrandpaScheduledChange(t *testing.T) { } func TestHandler_GrandpaForcedChange(t *testing.T) { - handler := newTestHandler(t) + handler, _ := newTestHandler(t) handler.Start() defer handler.Stop() @@ -161,7 +163,7 @@ func TestHandler_GrandpaForcedChange(t *testing.T) { } func TestHandler_GrandpaPauseAndResume(t *testing.T) { - handler := newTestHandler(t) + handler, _ := newTestHandler(t) handler.Start() defer handler.Stop() @@ -224,7 +226,7 @@ func TestHandler_GrandpaPauseAndResume(t *testing.T) { } func TestNextGrandpaAuthorityChange_OneChange(t *testing.T) { - handler := newTestHandler(t) + handler, _ := newTestHandler(t) handler.Start() defer handler.Stop() @@ -264,7 +266,7 @@ func TestNextGrandpaAuthorityChange_OneChange(t *testing.T) { } func TestNextGrandpaAuthorityChange_MultipleChanges(t *testing.T) { - handler := newTestHandler(t) + handler, _ := newTestHandler(t) handler.Start() defer handler.Stop() @@ -337,7 +339,7 @@ func TestNextGrandpaAuthorityChange_MultipleChanges(t *testing.T) { } func TestHandler_HandleBABEOnDisabled(t *testing.T) { - handler := newTestHandler(t) + handler, _ := newTestHandler(t) header := &types.Header{ Number: 1, } @@ -377,16 +379,13 @@ func createHeaderWithPreDigest(t *testing.T, slotNumber uint64) *types.Header { require.NoError(t, err) return &types.Header{ + Number: 1, Digest: digest, } } func TestHandler_HandleNextEpochData(t *testing.T) { - expData := common.MustHexToBytes("0x0108d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4801000000000000004d58630000000000000000000000000000000000000000000000000000000000") //nolint:lll - - handler := newTestHandler(t) - handler.Start() - defer handler.Stop() + expectedDigestBytes := common.MustHexToBytes("0x0108d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4801000000000000004d58630000000000000000000000000000000000000000000000000000000000") //nolint:lll keyring, err := keystore.NewSr25519Keyring() require.NoError(t, err) @@ -401,17 +400,19 @@ func TestHandler_HandleNextEpochData(t *testing.T) { Weight: 1, } - var digest = types.NewBabeConsensusDigest() - err = digest.Set(types.NextEpochData{ + nextEpochData := types.NextEpochData{ Authorities: []types.AuthorityRaw{authA, authB}, Randomness: [32]byte{77, 88, 99}, - }) + } + + digest := types.NewBabeConsensusDigest() + err = digest.Set(nextEpochData) require.NoError(t, err) data, err := scale.Marshal(digest) require.NoError(t, err) - require.Equal(t, expData, data) + require.Equal(t, expectedDigestBytes, data) d := &types.ConsensusDigest{ ConsensusEngineID: types.BabeEngineID, @@ -419,11 +420,33 @@ func TestHandler_HandleNextEpochData(t *testing.T) { } header := createHeaderWithPreDigest(t, 10) + handler, stateSrv := newTestHandler(t) err = handler.handleConsensusDigest(d, header) require.NoError(t, err) - stored, err := handler.epochState.(*state.EpochState).GetEpochData(1) + const targetEpoch = 1 + + blockHeaderKey := append([]byte("hdr"), header.Hash().ToBytes()...) + blockHeaderKey = append([]byte("block"), blockHeaderKey...) + err = stateSrv.DB().Put(blockHeaderKey, []byte{}) + require.NoError(t, err) + + handler.finalised = make(chan *types.FinalisationInfo, 1) + + const timeout = time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + handler.finalised <- &types.FinalisationInfo{ + Header: *header, + Round: 1, + SetID: 1, + } + + handler.handleBlockFinalisation(ctx) + + stored, err := handler.epochState.(*state.EpochState).GetEpochData(targetEpoch, nil) require.NoError(t, err) act, ok := digest.Value().(types.NextEpochData) @@ -437,16 +460,14 @@ func TestHandler_HandleNextEpochData(t *testing.T) { } func TestHandler_HandleNextConfigData(t *testing.T) { - handler := newTestHandler(t) - handler.Start() - defer handler.Stop() - var digest = types.NewBabeConsensusDigest() - err := digest.Set(types.NextConfigData{ + nextConfigData := types.NextConfigData{ C1: 1, C2: 8, SecondarySlots: 1, - }) + } + + err := digest.Set(nextConfigData) require.NoError(t, err) data, err := scale.Marshal(digest) @@ -459,15 +480,38 @@ func TestHandler_HandleNextConfigData(t *testing.T) { header := createHeaderWithPreDigest(t, 10) + handler, stateSrv := newTestHandler(t) + err = handler.handleConsensusDigest(d, header) require.NoError(t, err) + const targetEpoch = 1 + + blockHeaderKey := append([]byte("hdr"), header.Hash().ToBytes()...) + blockHeaderKey = append([]byte("block"), blockHeaderKey...) + err = stateSrv.DB().Put(blockHeaderKey, []byte{}) + require.NoError(t, err) + + handler.finalised = make(chan *types.FinalisationInfo, 1) + + const timeout = time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + handler.finalised <- &types.FinalisationInfo{ + Header: *header, + Round: 1, + SetID: 1, + } + + handler.handleBlockFinalisation(ctx) + act, ok := digest.Value().(types.NextConfigData) if !ok { t.Fatal() } - stored, err := handler.epochState.(*state.EpochState).GetConfigData(1) + stored, err := handler.epochState.(*state.EpochState).GetConfigData(targetEpoch, nil) require.NoError(t, err) require.Equal(t, act.ToConfigData(), stored) } diff --git a/dot/digest/interface.go b/dot/digest/interface.go index 75087fec67..22c2fda1e5 100644 --- a/dot/digest/interface.go +++ b/dot/digest/interface.go @@ -5,6 +5,7 @@ package digest import ( "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/grandpa" ) @@ -22,12 +23,17 @@ type EpochState interface { GetEpochForBlock(header *types.Header) (uint64, error) SetEpochData(epoch uint64, info *types.EpochData) error SetConfigData(epoch uint64, info *types.ConfigData) error + + StoreBABENextEpochData(epoch uint64, hash common.Hash, nextEpochData types.NextEpochData) + StoreBABENextConfigData(epoch uint64, hash common.Hash, nextEpochData types.NextConfigData) + FinalizeBABENextEpochData(epoch uint64) error + FinalizeBABENextConfigData(epoch uint64) error } // GrandpaState is the interface for the state.GrandpaState type GrandpaState interface { SetNextChange(authorities []grandpa.Voter, number uint) error - IncrementSetID() error + IncrementSetID() (newSetID uint64, err error) SetNextPause(number uint) error SetNextResume(number uint) error GetCurrentSetID() (uint64, error) diff --git a/dot/node.go b/dot/node.go index 0d3a626087..bb5344b063 100644 --- a/dot/node.go +++ b/dot/node.go @@ -287,7 +287,7 @@ func NewNode(cfg *Config, ks *keystore.GlobalKeystore) (*Node, error) { } nodeSrvcs = append(nodeSrvcs, dh) - coreSrvc, err := createCoreService(cfg, ks, stateSrvc, networkSrvc, dh) + coreSrvc, err := createCoreService(cfg, ks, stateSrvc, networkSrvc) if err != nil { return nil, fmt.Errorf("failed to create core service: %s", err) } diff --git a/dot/rpc/http_test.go b/dot/rpc/http_test.go index 800846e0d6..da5865586f 100644 --- a/dot/rpc/http_test.go +++ b/dot/rpc/http_test.go @@ -366,7 +366,6 @@ func externalIP() (string, error) { } //go:generate mockgen -destination=mock_telemetry_test.go -package $GOPACKAGE github.com/ChainSafe/gossamer/dot/telemetry Client -//go:generate mockgen -destination=mock_digesthandler_test.go -package $GOPACKAGE github.com/ChainSafe/gossamer/dot/core DigestHandler //go:generate mockgen -destination=mock_network_test.go -package $GOPACKAGE github.com/ChainSafe/gossamer/dot/core Network func newCoreServiceTest(t *testing.T) *core.Service { @@ -407,12 +406,6 @@ func newCoreServiceTest(t *testing.T) *core.Service { CodeSubstitutedState: stateSrvc.Base, } - digestHandler := NewMockDigestHandler(ctrl) - digestHandler.EXPECT(). - HandleDigests(gomock.AssignableToTypeOf(new(types.Header))). - AnyTimes() - cfg.DigestHandler = digestHandler - cfg.Keystore = keystore.NewGlobalKeystore() kp, err := sr25519.GenerateKeypair() require.NoError(t, err) diff --git a/dot/rpc/modules/author_integration_test.go b/dot/rpc/modules/author_integration_test.go index f90e484493..9930ece237 100644 --- a/dot/rpc/modules/author_integration_test.go +++ b/dot/rpc/modules/author_integration_test.go @@ -721,13 +721,11 @@ func setupStateAndPopulateTrieState(t *testing.T, basepath string, } //go:generate mockgen -destination=mock_code_substituted_state_test.go -package modules github.com/ChainSafe/gossamer/dot/core CodeSubstitutedState -//go:generate mockgen -destination=mock_digest_handler_test.go -package modules github.com/ChainSafe/gossamer/dot/core DigestHandler func newAuthorModule(t *testing.T, integrationTestController *integrationTestController) *AuthorModule { t.Helper() codeSubstitutedStateMock := NewMockCodeSubstitutedState(nil) - digestHandlerMock := NewMockDigestHandler(nil) cfg := &core.Config{ TransactionState: integrationTestController.stateSrv.Transaction, @@ -736,7 +734,6 @@ func newAuthorModule(t *testing.T, integrationTestController *integrationTestCon Network: integrationTestController.network, Keystore: integrationTestController.keystore, CodeSubstitutedState: codeSubstitutedStateMock, - DigestHandler: digestHandlerMock, } core2test, err := core.NewService(cfg) diff --git a/dot/rpc/modules/system_integration_test.go b/dot/rpc/modules/system_integration_test.go index 695227a132..cbd129b7ae 100644 --- a/dot/rpc/modules/system_integration_test.go +++ b/dot/rpc/modules/system_integration_test.go @@ -375,8 +375,6 @@ func newCoreService(t *testing.T, srvc *state.Service) *core.Service { gomock.AssignableToTypeOf(new(network.TransactionMessage))). AnyTimes() - digestHandlerMock := NewMockDigestHandler(nil) - cfg := &core.Config{ Runtime: rt, Keystore: ks, @@ -386,7 +384,6 @@ func newCoreService(t *testing.T, srvc *state.Service) *core.Service { EpochState: srvc.Epoch, Network: mocknet, CodeSubstitutedState: srvc.Base, - DigestHandler: digestHandlerMock, } s, err := core.NewService(cfg) diff --git a/dot/services.go b/dot/services.go index 49c2c1f03a..0cdb74a278 100644 --- a/dot/services.go +++ b/dot/services.go @@ -229,7 +229,7 @@ func createBABEService(cfg *Config, st *state.Service, ks keystore.Keystore, // createCoreService creates the core service from the provided core configuration func createCoreService(cfg *Config, ks *keystore.GlobalKeystore, - st *state.Service, net *network.Service, dh *digest.Handler) ( + st *state.Service, net *network.Service) ( *core.Service, error) { logger.Debug("creating core service" + asAuthority(cfg.Core.Roles == types.AuthorityRole) + @@ -254,7 +254,6 @@ func createCoreService(cfg *Config, ks *keystore.GlobalKeystore, TransactionState: st.Transaction, Keystore: ks, Network: net, - DigestHandler: dh, CodeSubstitutes: codeSubs, CodeSubstitutedState: st.Base, } diff --git a/dot/services_integration_test.go b/dot/services_integration_test.go index f00bd55ca7..c6511ecd6c 100644 --- a/dot/services_integration_test.go +++ b/dot/services_integration_test.go @@ -59,10 +59,7 @@ func TestCreateCoreService(t *testing.T) { networkSrvc := &network.Service{} - dh, err := createDigestHandler(cfg.Log.DigestLvl, stateSrvc) - require.NoError(t, err) - - coreSrvc, err := createCoreService(cfg, ks, stateSrvc, networkSrvc, dh) + coreSrvc, err := createCoreService(cfg, ks, stateSrvc, networkSrvc) require.NoError(t, err) require.NotNil(t, coreSrvc) } @@ -105,10 +102,7 @@ func TestCreateSyncService(t *testing.T) { ver, err := createBlockVerifier(stateSrvc) require.NoError(t, err) - dh, err := createDigestHandler(cfg.Log.DigestLvl, stateSrvc) - require.NoError(t, err) - - coreSrvc, err := createCoreService(cfg, ks, stateSrvc, &network.Service{}, dh) + coreSrvc, err := createCoreService(cfg, ks, stateSrvc, &network.Service{}) require.NoError(t, err) _, err = newSyncService(cfg, stateSrvc, &grandpa.Service{}, ver, coreSrvc, &network.Service{}, nil) @@ -162,10 +156,7 @@ func TestCreateRPCService(t *testing.T) { err = loadRuntime(cfg, ns, stateSrvc, ks, networkSrvc) require.NoError(t, err) - dh, err := createDigestHandler(cfg.Log.DigestLvl, stateSrvc) - require.NoError(t, err) - - coreSrvc, err := createCoreService(cfg, ks, stateSrvc, networkSrvc, dh) + coreSrvc, err := createCoreService(cfg, ks, stateSrvc, networkSrvc) require.NoError(t, err) sysSrvc, err := createSystemService(&cfg.System, stateSrvc) @@ -208,10 +199,7 @@ func TestCreateBABEService(t *testing.T) { err = loadRuntime(cfg, ns, stateSrvc, ks, &network.Service{}) require.NoError(t, err) - dh, err := createDigestHandler(cfg.Log.DigestLvl, stateSrvc) - require.NoError(t, err) - - coreSrvc, err := createCoreService(cfg, ks, stateSrvc, &network.Service{}, dh) + coreSrvc, err := createCoreService(cfg, ks, stateSrvc, &network.Service{}) require.NoError(t, err) bs, err := createBABEService(cfg, stateSrvc, ks.Babe, coreSrvc, nil) @@ -312,10 +300,7 @@ func TestNewWebSocketServer(t *testing.T) { err = loadRuntime(cfg, ns, stateSrvc, ks, networkSrvc) require.NoError(t, err) - dh, err := createDigestHandler(cfg.Log.DigestLvl, stateSrvc) - require.NoError(t, err) - - coreSrvc, err := createCoreService(cfg, ks, stateSrvc, networkSrvc, dh) + coreSrvc, err := createCoreService(cfg, ks, stateSrvc, networkSrvc) require.NoError(t, err) sysSrvc, err := createSystemService(&cfg.System, stateSrvc) diff --git a/dot/state/block.go b/dot/state/block.go index b3da60f1c1..0ce071a6e4 100644 --- a/dot/state/block.go +++ b/dot/state/block.go @@ -183,7 +183,8 @@ func (bs *BlockState) GenesisHash() common.Hash { return bs.genesisHash } -// HasHeader returns if the db contains a header with the given hash +// HasHeader returns true if the hash is part of the unfinalised blocks in-memory or +// persisted in the database. func (bs *BlockState) HasHeader(hash common.Hash) (bool, error) { if bs.unfinalisedBlocks.getBlock(hash) != nil { return true, nil @@ -192,6 +193,11 @@ func (bs *BlockState) HasHeader(hash common.Hash) (bool, error) { return bs.db.Has(headerKey(hash)) } +// HasHeaderInDatabase returns true if the database contains a header with the given hash +func (bs *BlockState) HasHeaderInDatabase(hash common.Hash) (bool, error) { + return bs.db.Has(headerKey(hash)) +} + // GetHeader returns a BlockHeader for a given hash func (bs *BlockState) GetHeader(hash common.Hash) (header *types.Header, err error) { header = bs.unfinalisedBlocks.getBlockHeader(hash) diff --git a/dot/state/block_finalisation.go b/dot/state/block_finalisation.go index e5adbc049e..d7677bebc4 100644 --- a/dot/state/block_finalisation.go +++ b/dot/state/block_finalisation.go @@ -117,6 +117,7 @@ func (bs *BlockState) GetHighestFinalisedHeader() (*types.Header, error) { func (bs *BlockState) SetFinalisedHash(hash common.Hash, round, setID uint64) error { bs.Lock() defer bs.Unlock() + has, _ := bs.HasHeader(hash) if !has { return fmt.Errorf("cannot finalise unknown block %s", hash) diff --git a/dot/state/epoch.go b/dot/state/epoch.go index a4b7d3c2c6..d77ae8fc32 100644 --- a/dot/state/epoch.go +++ b/dot/state/epoch.go @@ -7,13 +7,21 @@ import ( "encoding/binary" "errors" "fmt" + "sync" "time" "github.com/ChainSafe/chaindb" "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/pkg/scale" ) +var ( + ErrEpochNotInMemory = errors.New("epoch not found in memory map") + errHashNotInMemory = errors.New("hash not found in memory map") + errHashNotPersisted = errors.New("hash with next epoch not found in database") +) + var ( epochPrefix = "epoch" epochLengthKey = []byte("epochlength") @@ -45,6 +53,14 @@ type EpochState struct { blockState *BlockState epochLength uint64 // measured in slots skipToEpoch uint64 + + nextEpochDataLock sync.RWMutex + // nextEpochData follows the format map[epoch]map[block hash]next epoch data + nextEpochData map[uint64]map[common.Hash]types.NextEpochData + + nextConfigDataLock sync.RWMutex + // nextConfigData follows the format map[epoch]map[block hash]next config data + nextConfigData map[uint64]map[common.Hash]types.NextConfigData } // NewEpochStateFromGenesis returns a new EpochState given information for the first epoch, fetched from the runtime @@ -68,10 +84,12 @@ func NewEpochStateFromGenesis(db chaindb.Database, blockState *BlockState, } s := &EpochState{ - baseState: NewBaseState(db), - blockState: blockState, - db: epochDB, - epochLength: genesisConfig.EpochLength, + baseState: NewBaseState(db), + blockState: blockState, + db: epochDB, + epochLength: genesisConfig.EpochLength, + nextEpochData: make(map[uint64]map[common.Hash]types.NextEpochData), + nextConfigData: make(map[uint64]map[common.Hash]types.NextConfigData), } auths, err := types.BABEAuthorityRawToAuthority(genesisConfig.GenesisAuthorities) @@ -126,11 +144,13 @@ func NewEpochState(db chaindb.Database, blockState *BlockState) (*EpochState, er } return &EpochState{ - baseState: baseState, - blockState: blockState, - db: chaindb.NewTable(db, epochPrefix), - epochLength: epochLength, - skipToEpoch: skipToEpoch, + baseState: baseState, + blockState: blockState, + db: chaindb.NewTable(db, epochPrefix), + epochLength: epochLength, + skipToEpoch: skipToEpoch, + nextEpochData: make(map[uint64]map[common.Hash]types.NextEpochData), + nextConfigData: make(map[uint64]map[common.Hash]types.NextConfigData), }, nil } @@ -220,8 +240,32 @@ func (s *EpochState) SetEpochData(epoch uint64, info *types.EpochData) error { return s.db.Put(epochDataKey(epoch), enc) } -// GetEpochData returns the epoch data for a given epoch -func (s *EpochState) GetEpochData(epoch uint64) (*types.EpochData, error) { +// GetEpochData returns the epoch data for a given epoch persisted in database +// otherwise will try to get the data from the in-memory map using the header +// if the header params is nil then it will search only in database +func (s *EpochState) GetEpochData(epoch uint64, header *types.Header) (*types.EpochData, error) { + epochData, err := s.getEpochDataInDatabase(epoch) + if err == nil && epochData != nil { + return epochData, nil + } + + if err != nil && !errors.Is(err, chaindb.ErrKeyNotFound) { + return nil, fmt.Errorf("failed to get epoch data from database: %w", err) + } else if header == nil { + // if no header is given then skip the lookup in-memory + return epochData, nil + } + + epochData, err = s.getEpochDataInMemory(epoch, header) + if err != nil { + return nil, fmt.Errorf("failed to get epoch data from memory: %w", err) + } + + return epochData, nil +} + +// getEpochDataInDatabase returns the epoch data for a given epoch persisted in database +func (s *EpochState) getEpochDataInDatabase(epoch uint64) (*types.EpochData, error) { enc, err := s.db.Get(epochDataKey(epoch)) if err != nil { return nil, err @@ -236,6 +280,32 @@ func (s *EpochState) GetEpochData(epoch uint64) (*types.EpochData, error) { return raw.ToEpochData() } +// getEpochDataInMemory retrieves the right epoch data that belongs to the header parameter +func (s *EpochState) getEpochDataInMemory(epoch uint64, header *types.Header) (*types.EpochData, error) { + s.nextEpochDataLock.RLock() + defer s.nextEpochDataLock.RUnlock() + + atEpoch, has := s.nextEpochData[epoch] + if !has { + return nil, fmt.Errorf("%w: %d", ErrEpochNotInMemory, epoch) + } + + headerHash := header.Hash() + + for hash, value := range atEpoch { + isDescendant, err := s.blockState.IsDescendantOf(hash, headerHash) + if err != nil { + return nil, fmt.Errorf("cannot verify the ancestry: %w", err) + } + + if isDescendant { + return value.ToEpochData() + } + } + + return nil, fmt.Errorf("%w: %s", errHashNotInMemory, headerHash) +} + // GetLatestEpochData returns the EpochData for the current epoch func (s *EpochState) GetLatestEpochData() (*types.EpochData, error) { curr, err := s.GetCurrentEpoch() @@ -243,12 +313,25 @@ func (s *EpochState) GetLatestEpochData() (*types.EpochData, error) { return nil, err } - return s.GetEpochData(curr) + return s.GetEpochData(curr, nil) } // HasEpochData returns whether epoch data exists for a given epoch func (s *EpochState) HasEpochData(epoch uint64) (bool, error) { - return s.db.Has(epochDataKey(epoch)) + has, err := s.db.Has(epochDataKey(epoch)) + if err == nil && has { + return has, nil + } + + if !errors.Is(chaindb.ErrKeyNotFound, err) { + return false, fmt.Errorf("cannot check database for epoch key %d: %w", epoch, err) + } + + s.nextEpochDataLock.Lock() + defer s.nextEpochDataLock.Unlock() + + _, has = s.nextEpochData[epoch] + return has, nil } // SetConfigData sets the BABE config data for a given epoch @@ -272,8 +355,32 @@ func (s *EpochState) setLatestConfigData(epoch uint64) error { return s.db.Put(latestConfigDataKey, buf) } -// GetConfigData returns the BABE config data for a given epoch -func (s *EpochState) GetConfigData(epoch uint64) (*types.ConfigData, error) { +// GetConfigData returns the config data for a given epoch persisted in database +// otherwise tries to get the data from the in-memory map using the header. +// If the header params is nil then it will search only in the database +func (s *EpochState) GetConfigData(epoch uint64, header *types.Header) (*types.ConfigData, error) { + configData, err := s.getConfigDataInDatabase(epoch) + if err == nil && configData != nil { + return configData, nil + } + + if err != nil && !errors.Is(err, chaindb.ErrKeyNotFound) { + return nil, fmt.Errorf("failed to get config data from database: %w", err) + } else if header == nil { + // if no header is given then skip the lookup in-memory + return configData, nil + } + + configData, err = s.getConfigDataInMemory(epoch, header) + if err != nil { + return nil, fmt.Errorf("failed to get config data from memory: %w", err) + } + + return configData, nil +} + +// getConfigDataInDatabase returns the BABE config data for a given epoch persisted in database +func (s *EpochState) getConfigDataInDatabase(epoch uint64) (*types.ConfigData, error) { enc, err := s.db.Get(configDataKey(epoch)) if err != nil { return nil, err @@ -288,6 +395,32 @@ func (s *EpochState) GetConfigData(epoch uint64) (*types.ConfigData, error) { return info, nil } +// getConfigDataInMemory retrieves the BABE config data for a given epoch that belongs to the header parameter +func (s *EpochState) getConfigDataInMemory(epoch uint64, header *types.Header) (*types.ConfigData, error) { + s.nextConfigDataLock.RLock() + defer s.nextConfigDataLock.RUnlock() + + atEpoch, has := s.nextConfigData[epoch] + if !has { + return nil, fmt.Errorf("%w: %d", ErrEpochNotInMemory, epoch) + } + + headerHash := header.Hash() + + for hash, value := range atEpoch { + isDescendant, err := s.blockState.IsDescendantOf(hash, headerHash) + if err != nil { + return nil, fmt.Errorf("cannot verify the ancestry: %w", err) + } + + if isDescendant { + return value.ToConfigData(), nil + } + } + + return nil, fmt.Errorf("%w: %s", errHashNotInMemory, headerHash) +} + // GetLatestConfigData returns the most recently set ConfigData func (s *EpochState) GetLatestConfigData() (*types.ConfigData, error) { b, err := s.db.Get(latestConfigDataKey) @@ -296,12 +429,25 @@ func (s *EpochState) GetLatestConfigData() (*types.ConfigData, error) { } epoch := binary.LittleEndian.Uint64(b) - return s.GetConfigData(epoch) + return s.GetConfigData(epoch, nil) } // HasConfigData returns whether config data exists for a given epoch func (s *EpochState) HasConfigData(epoch uint64) (bool, error) { - return s.db.Has(configDataKey(epoch)) + has, err := s.db.Has(configDataKey(epoch)) + if err == nil && has { + return has, nil + } + + if err != nil && !errors.Is(chaindb.ErrKeyNotFound, err) { + return false, fmt.Errorf("cannot check database for epoch key %d: %w", epoch, err) + } + + s.nextConfigDataLock.Lock() + defer s.nextConfigDataLock.Unlock() + + _, has = s.nextConfigData[epoch] + return has, nil } // GetStartSlotForEpoch returns the first slot in the given epoch. @@ -365,3 +511,115 @@ func (s *EpochState) SkipVerify(header *types.Header) (bool, error) { return false, nil } + +// StoreBABENextEpochData stores the types.NextEpochData under epoch and hash keys +func (s *EpochState) StoreBABENextEpochData(epoch uint64, hash common.Hash, nextEpochData types.NextEpochData) { + s.nextEpochDataLock.Lock() + defer s.nextEpochDataLock.Unlock() + + _, has := s.nextEpochData[epoch] + if !has { + s.nextEpochData[epoch] = make(map[common.Hash]types.NextEpochData) + } + s.nextEpochData[epoch][hash] = nextEpochData +} + +// StoreBABENextConfigData stores the types.NextConfigData under epoch and hash keys +func (s *EpochState) StoreBABENextConfigData(epoch uint64, hash common.Hash, nextConfigData types.NextConfigData) { + s.nextConfigDataLock.Lock() + defer s.nextConfigDataLock.Unlock() + + _, has := s.nextConfigData[epoch] + if !has { + s.nextConfigData[epoch] = make(map[common.Hash]types.NextConfigData) + } + s.nextConfigData[epoch][hash] = nextConfigData +} + +// FinalizeBABENextEpochData stores the right types.NextEpochData by +// getting the set of hashes from the received epoch and for each hash +// check if the header is in the database then it's been finalized and +// thus we can also set the corresponding EpochData in the database +func (s *EpochState) FinalizeBABENextEpochData(epoch uint64) error { + s.nextEpochDataLock.Lock() + defer s.nextEpochDataLock.Unlock() + + finalizedNextEpochData, err := lookupForNextEpochPersistedHash(s.nextEpochData, s, epoch) + if err != nil { + return fmt.Errorf("cannot find next epoch data: %w", err) + } + + ed, err := finalizedNextEpochData.ToEpochData() + if err != nil { + return fmt.Errorf("cannot transform epoch data: %w", err) + } + + err = s.SetEpochData(epoch, ed) + if err != nil { + return fmt.Errorf("cannot set epoch data: %w", err) + } + + // remove previous epochs from the memory + for e := range s.nextEpochData { + if e <= epoch { + delete(s.nextEpochData, e) + } + } + + return nil +} + +// FinalizeBABENextConfigData stores the right types.NextConfigData by +// getting the set of hashes from the received epoch and for each hash +// check if the header is in the database then it's been finalized and +// thus we can also set the corresponding NextConfigData in the database +func (s *EpochState) FinalizeBABENextConfigData(epoch uint64) error { + s.nextConfigDataLock.Lock() + defer s.nextConfigDataLock.Unlock() + + finalizedNextConfigData, err := lookupForNextEpochPersistedHash(s.nextConfigData, s, epoch) + if err != nil { + return fmt.Errorf("cannot find next config data: %w", err) + } + + cd := finalizedNextConfigData.ToConfigData() + err = s.SetConfigData(epoch, cd) + if err != nil { + return fmt.Errorf("cannot set config data: %w", err) + } + + // remove previous epochs from the memory + for e := range s.nextConfigData { + if e <= epoch { + delete(s.nextConfigData, e) + } + } + + return nil +} + +// lookupForNextEpochPersistedHash given a specific epoch (the key) will go through the hashes looking +// for a database persisted hash (belonging to the finalized chain) +// which contains the right configuration to be persisted and safely used +func lookupForNextEpochPersistedHash[T types.NextConfigData | types.NextEpochData]( + nextEpochMap map[uint64]map[common.Hash]T, es *EpochState, epoch uint64) (next *T, err error) { + hashes, has := nextEpochMap[epoch] + if !has { + return nil, ErrEpochNotInMemory + } + + for hash, inMemory := range hashes { + persisted, err := es.blockState.HasHeaderInDatabase(hash) + if err != nil { + return nil, fmt.Errorf("failed to check header exists in database: %w", err) + } + + if !persisted { + continue + } + + return &inMemory, nil + } + + return nil, errHashNotPersisted +} diff --git a/dot/state/epoch_test.go b/dot/state/epoch_test.go index 24946c93c4..f3b4247890 100644 --- a/dot/state/epoch_test.go +++ b/dot/state/epoch_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/crypto/sr25519" "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/pkg/scale" @@ -72,7 +73,7 @@ func TestEpochState_EpochData(t *testing.T) { err = s.SetEpochData(1, info) require.NoError(t, err) - res, err := s.GetEpochData(1) + res, err := s.GetEpochData(1, nil) require.NoError(t, err) require.Equal(t, info.Randomness, res.Randomness) @@ -127,7 +128,7 @@ func TestEpochState_ConfigData(t *testing.T) { err := s.SetConfigData(1, data) require.NoError(t, err) - ret, err := s.GetConfigData(1) + ret, err := s.GetConfigData(1, nil) require.NoError(t, err) require.Equal(t, data, ret) @@ -224,3 +225,385 @@ func TestEpochState_GetEpochFromTime(t *testing.T) { require.NoError(t, err) require.Equal(t, uint64(99), epoch) } + +type inMemoryNextEpochData struct { + epoch uint64 + hashes []common.Hash + nextEpochDatas []types.NextEpochData +} + +func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { + /* + * Setup the services: StateService, DigestHandler, EpochState + * and VerificationManager + */ + + keyring, _ := keystore.NewSr25519Keyring() + keyPairs := []*sr25519.Keypair{ + keyring.KeyAlice, keyring.KeyBob, keyring.KeyCharlie, + keyring.KeyDave, keyring.KeyEve, keyring.KeyFerdie, + keyring.KeyGeorge, keyring.KeyHeather, keyring.KeyIan, + } + + authorities := make([]types.AuthorityRaw, len(keyPairs)) + for i, keyPair := range keyPairs { + authorities[i] = types.AuthorityRaw{ + Key: keyPair.Public().(*sr25519.PublicKey).AsBytes(), + } + } + + tests := map[string]struct { + finalizeHash common.Hash + inMemoryEpoch []inMemoryNextEpochData + finalizeEpoch uint64 + expectErr error + shouldRemainInMemory int + }{ + "store_and_finalize_successfully": { + shouldRemainInMemory: 1, + finalizeEpoch: 2, + finalizeHash: common.MustHexToHash("0x68a27df5a52ff2251df2cc8368f7dcefb305a13bb3d89b65c8fb070f23877f2c"), + inMemoryEpoch: []inMemoryNextEpochData{ + { + epoch: 1, + hashes: []common.Hash{ + common.MustHexToHash("0x9da3ce2785da743bfbc13449db7dcb7a69c07ca914276d839abe7bedc6ac8fed"), + common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"), + common.MustHexToHash("0xc0096358534ec8d21d01d34b836eed476a1c343f8724fa2153dc0725ad797a90"), + }, + nextEpochDatas: []types.NextEpochData{ + { + Authorities: authorities[:3], + Randomness: [32]byte{1}, + }, + { + Authorities: authorities[3:6], + Randomness: [32]byte{2}, + }, + { + Authorities: authorities[6:], + Randomness: [32]byte{3}, + }, + }, + }, + { + epoch: 2, + hashes: []common.Hash{ + common.MustHexToHash("0x5b940c7fc0a1c5a58e4d80c5091dd003303b8f18e90a989f010c1be6f392bed1"), + common.MustHexToHash("0xd380bee22de487a707cbda65dd9d4e2188f736908c42cf390c8919d4f7fc547c"), + common.MustHexToHash("0x68a27df5a52ff2251df2cc8368f7dcefb305a13bb3d89b65c8fb070f23877f2c"), + }, + nextEpochDatas: []types.NextEpochData{ + { + Authorities: authorities[6:], + Randomness: [32]byte{1}, + }, + { + Authorities: authorities[:3], + Randomness: [32]byte{2}, + }, + { + Authorities: authorities[3:6], + Randomness: [32]byte{3}, + }, + }, + }, + { + epoch: 3, + hashes: []common.Hash{ + common.MustHexToHash("0xab5c9230a7dde8bb90a6728ba4a0165423294dac14336b1443f865b796ff682c"), + }, + nextEpochDatas: []types.NextEpochData{ + { + Authorities: authorities[6:], + Randomness: [32]byte{1}, + }, + }, + }, + }, + }, + "cannot_finalize_hash_not_stored": { + shouldRemainInMemory: 1, + finalizeEpoch: 1, + finalizeHash: common.Hash{}, // finalize when the hash does not exists + expectErr: errHashNotPersisted, + inMemoryEpoch: []inMemoryNextEpochData{ + { + epoch: 1, + hashes: []common.Hash{ + common.MustHexToHash("0x9da3ce2785da743bfbc13449db7dcb7a69c07ca914276d839abe7bedc6ac8fed"), + common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"), + common.MustHexToHash("0xc0096358534ec8d21d01d34b836eed476a1c343f8724fa2153dc0725ad797a90"), + }, + nextEpochDatas: []types.NextEpochData{ + { + Authorities: authorities[:3], + Randomness: [32]byte{1}, + }, + { + Authorities: authorities[3:6], + Randomness: [32]byte{2}, + }, + { + Authorities: authorities[6:], + Randomness: [32]byte{3}, + }, + }, + }, + }, + }, + "cannot_finalize_in_memory_epoch_not_found": { + shouldRemainInMemory: 1, + finalizeEpoch: 3, // try to finalize a epoch that does not exists + finalizeHash: common.Hash{}, + expectErr: ErrEpochNotInMemory, + inMemoryEpoch: []inMemoryNextEpochData{ + { + epoch: 1, + hashes: []common.Hash{ + common.MustHexToHash("0x9da3ce2785da743bfbc13449db7dcb7a69c07ca914276d839abe7bedc6ac8fed"), + common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"), + common.MustHexToHash("0xc0096358534ec8d21d01d34b836eed476a1c343f8724fa2153dc0725ad797a90"), + }, + nextEpochDatas: []types.NextEpochData{ + { + Authorities: authorities[:3], + Randomness: [32]byte{1}, + }, + { + Authorities: authorities[3:6], + Randomness: [32]byte{2}, + }, + { + Authorities: authorities[6:], + Randomness: [32]byte{3}, + }, + }, + }, + }, + }, + } + + for testName, tt := range tests { + t.Run(testName, func(t *testing.T) { + epochState := newEpochStateFromGenesis(t) + + for _, e := range tt.inMemoryEpoch { + for i, hash := range e.hashes { + epochState.StoreBABENextEpochData(e.epoch, hash, e.nextEpochDatas[i]) + } + } + + require.Len(t, epochState.nextEpochData, len(tt.inMemoryEpoch)) + + expectedNextEpochData := epochState.nextEpochData[tt.finalizeEpoch][tt.finalizeHash] + + err := epochState.blockState.db.Put(headerKey(tt.finalizeHash), []byte{}) + require.NoError(t, err) + + err = epochState.FinalizeBABENextEpochData(tt.finalizeEpoch) + if tt.expectErr != nil { + require.ErrorIs(t, err, tt.expectErr) + } else { + require.NoError(t, err) + + expected, err := expectedNextEpochData.ToEpochData() + require.NoError(t, err) + + gotNextEpochData, err := epochState.GetEpochData(tt.finalizeEpoch, nil) + require.NoError(t, err) + + require.Equal(t, expected, gotNextEpochData) + } + + // should delete previous epochs since the most up to date epoch is stored + require.Len(t, epochState.nextEpochData, tt.shouldRemainInMemory) + }) + } +} + +type inMemotyNextConfighData struct { + epoch uint64 + hashes []common.Hash + nextConfigDatas []types.NextConfigData +} + +func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { + tests := map[string]struct { + finalizeHash common.Hash + inMemoryEpoch []inMemotyNextConfighData + finalizeEpoch uint64 + expectErr error + shouldRemainInMemory int + }{ + "store_and_finalize_successfully": { + shouldRemainInMemory: 1, + finalizeEpoch: 2, + finalizeHash: common.MustHexToHash("0x68a27df5a52ff2251df2cc8368f7dcefb305a13bb3d89b65c8fb070f23877f2c"), + inMemoryEpoch: []inMemotyNextConfighData{ + { + epoch: 1, + hashes: []common.Hash{ + common.MustHexToHash("0x9da3ce2785da743bfbc13449db7dcb7a69c07ca914276d839abe7bedc6ac8fed"), + common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"), + common.MustHexToHash("0xc0096358534ec8d21d01d34b836eed476a1c343f8724fa2153dc0725ad797a90"), + }, + nextConfigDatas: []types.NextConfigData{ + { + C1: 1, + C2: 2, + SecondarySlots: 0, + }, + { + C1: 2, + C2: 3, + SecondarySlots: 1, + }, + { + C1: 3, + C2: 4, + SecondarySlots: 0, + }, + }, + }, + { + epoch: 2, + hashes: []common.Hash{ + common.MustHexToHash("0x5b940c7fc0a1c5a58e4d80c5091dd003303b8f18e90a989f010c1be6f392bed1"), + common.MustHexToHash("0xd380bee22de487a707cbda65dd9d4e2188f736908c42cf390c8919d4f7fc547c"), + common.MustHexToHash("0x68a27df5a52ff2251df2cc8368f7dcefb305a13bb3d89b65c8fb070f23877f2c"), + }, + nextConfigDatas: []types.NextConfigData{ + { + C1: 1, + C2: 2, + SecondarySlots: 0, + }, + { + C1: 2, + C2: 3, + SecondarySlots: 1, + }, + { + C1: 3, + C2: 4, + SecondarySlots: 0, + }, + }, + }, + { + epoch: 3, + hashes: []common.Hash{ + common.MustHexToHash("0xab5c9230a7dde8bb90a6728ba4a0165423294dac14336b1443f865b796ff682c"), + }, + nextConfigDatas: []types.NextConfigData{ + { + C1: 1, + C2: 2, + SecondarySlots: 0, + }, + }, + }, + }, + }, + "cannot_finalize_hash_doesnt_exists": { + shouldRemainInMemory: 1, + finalizeEpoch: 1, + finalizeHash: common.Hash{}, // finalize when the hash does not exists + expectErr: errHashNotPersisted, + inMemoryEpoch: []inMemotyNextConfighData{ + { + epoch: 1, + hashes: []common.Hash{ + common.MustHexToHash("0x9da3ce2785da743bfbc13449db7dcb7a69c07ca914276d839abe7bedc6ac8fed"), + common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"), + common.MustHexToHash("0xc0096358534ec8d21d01d34b836eed476a1c343f8724fa2153dc0725ad797a90"), + }, + nextConfigDatas: []types.NextConfigData{ + { + C1: 1, + C2: 2, + SecondarySlots: 0, + }, + { + C1: 2, + C2: 3, + SecondarySlots: 1, + }, + { + C1: 3, + C2: 4, + SecondarySlots: 0, + }, + }, + }, + }, + }, + "cannot_finalize_in_memory_epoch_not_found": { + shouldRemainInMemory: 1, + finalizeEpoch: 3, // try to finalize a epoch that does not exists + finalizeHash: common.Hash{}, + expectErr: ErrEpochNotInMemory, + inMemoryEpoch: []inMemotyNextConfighData{ + { + epoch: 1, + hashes: []common.Hash{ + common.MustHexToHash("0x9da3ce2785da743bfbc13449db7dcb7a69c07ca914276d839abe7bedc6ac8fed"), + common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"), + common.MustHexToHash("0xc0096358534ec8d21d01d34b836eed476a1c343f8724fa2153dc0725ad797a90"), + }, + nextConfigDatas: []types.NextConfigData{ + { + C1: 1, + C2: 2, + SecondarySlots: 0, + }, + { + C1: 2, + C2: 3, + SecondarySlots: 1, + }, + { + C1: 3, + C2: 4, + SecondarySlots: 0, + }, + }, + }, + }, + }, + } + + for testName, tt := range tests { + t.Run(testName, func(t *testing.T) { + epochState := newEpochStateFromGenesis(t) + + for _, e := range tt.inMemoryEpoch { + for i, hash := range e.hashes { + epochState.StoreBABENextConfigData(e.epoch, hash, e.nextConfigDatas[i]) + } + } + + require.Len(t, epochState.nextConfigData, len(tt.inMemoryEpoch)) + + expectedConfigData := epochState.nextConfigData[tt.finalizeEpoch][tt.finalizeHash] + + err := epochState.blockState.db.Put(headerKey(tt.finalizeHash), []byte{}) + require.NoError(t, err) + + err = epochState.FinalizeBABENextConfigData(tt.finalizeEpoch) + if tt.expectErr != nil { + require.ErrorIs(t, err, tt.expectErr) + } else { + require.NoError(t, err) + + gotConfigData, err := epochState.GetConfigData(tt.finalizeEpoch, nil) + require.NoError(t, err) + require.Equal(t, expectedConfigData.ToConfigData(), gotConfigData) + } + + // should delete previous epochs since the most up to date epoch is stored + require.Len(t, epochState.nextConfigData, tt.shouldRemainInMemory) + }) + } +} diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 0983e9614d..7b29387e25 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -6,6 +6,7 @@ package state import ( "encoding/binary" "errors" + "fmt" "github.com/ChainSafe/chaindb" "github.com/ChainSafe/gossamer/dot/types" @@ -159,14 +160,19 @@ func (s *GrandpaState) SetNextChange(authorities []types.GrandpaVoter, number ui } // IncrementSetID increments the set ID -func (s *GrandpaState) IncrementSetID() error { +func (s *GrandpaState) IncrementSetID() (newSetID uint64, err error) { currSetID, err := s.GetCurrentSetID() if err != nil { - return err + return 0, fmt.Errorf("cannot get current set ID: %w", err) } - nextSetID := currSetID + 1 - return s.setCurrentSetID(nextSetID) + newSetID = currSetID + 1 + err = s.setCurrentSetID(newSetID) + if err != nil { + return 0, fmt.Errorf("cannot set current set ID: %w", err) + } + + return newSetID, nil } // setSetIDChangeAtBlock sets a set ID change at a certain block diff --git a/dot/state/grandpa_test.go b/dot/state/grandpa_test.go index a22add98fb..20bf45869a 100644 --- a/dot/state/grandpa_test.go +++ b/dot/state/grandpa_test.go @@ -60,10 +60,7 @@ func TestGrandpaState_IncrementSetID(t *testing.T) { gs, err := NewGrandpaStateFromGenesis(db, testAuths) require.NoError(t, err) - err = gs.IncrementSetID() - require.NoError(t, err) - - setID, err := gs.GetCurrentSetID() + setID, err := gs.IncrementSetID() require.NoError(t, err) require.Equal(t, genesisSetID+1, setID) } @@ -88,7 +85,7 @@ func TestGrandpaState_GetSetIDByBlockNumber(t *testing.T) { require.NoError(t, err) require.Equal(t, genesisSetID+1, setID) - err = gs.IncrementSetID() + newSetID, err := gs.IncrementSetID() require.NoError(t, err) setID, err = gs.GetSetIDByBlockNumber(100) @@ -98,6 +95,7 @@ func TestGrandpaState_GetSetIDByBlockNumber(t *testing.T) { setID, err = gs.GetSetIDByBlockNumber(101) require.NoError(t, err) require.Equal(t, genesisSetID+1, setID) + require.Equal(t, genesisSetID+1, newSetID) } func TestGrandpaState_LatestRound(t *testing.T) { diff --git a/dot/types/digest.go b/dot/types/digest.go index 73894c160a..e0796dcca1 100644 --- a/dot/types/digest.go +++ b/dot/types/digest.go @@ -80,7 +80,7 @@ type ConsensusDigest struct { func (d ConsensusDigest) Index() uint { return 4 } // String returns the digest as a string -func (d *ConsensusDigest) String() string { +func (d ConsensusDigest) String() string { return fmt.Sprintf("ConsensusDigest ConsensusEngineID=%s Data=0x%x", d.ConsensusEngineID.ToBytes(), d.Data) } diff --git a/lib/babe/epoch.go b/lib/babe/epoch.go index 7d556767db..379e53be1c 100644 --- a/lib/babe/epoch.go +++ b/lib/babe/epoch.go @@ -100,7 +100,7 @@ func (b *Service) getEpochDataAndStartSlot(epoch uint64) (*epochData, uint64, er return nil, 0, fmt.Errorf("%w: for epoch %d", errNoEpochData, epoch) } - data, err := b.epochState.GetEpochData(epoch) + data, err := b.epochState.GetEpochData(epoch, nil) if err != nil { return nil, 0, fmt.Errorf("cannot get epoch data for epoch %d: %w", epoch, err) } @@ -117,7 +117,7 @@ func (b *Service) getEpochDataAndStartSlot(epoch uint64) (*epochData, uint64, er var cfgData *types.ConfigData if has { - cfgData, err = b.epochState.GetConfigData(epoch) + cfgData, err = b.epochState.GetConfigData(epoch, nil) if err != nil { return nil, 0, fmt.Errorf("cannot get config data for epoch %d: %w", epoch, err) } diff --git a/lib/babe/epoch_integration_test.go b/lib/babe/epoch_integration_test.go index 2b285a2c01..dd2adc3fbc 100644 --- a/lib/babe/epoch_integration_test.go +++ b/lib/babe/epoch_integration_test.go @@ -42,7 +42,7 @@ func TestInitiateEpoch_Epoch1(t *testing.T) { Weight: 1, } - data, err := bs.epochState.GetEpochData(0) + data, err := bs.epochState.GetEpochData(0, nil) require.NoError(t, err) data.Authorities = []types.Authority{auth} err = bs.epochState.SetEpochData(1, data) diff --git a/lib/babe/epoch_test.go b/lib/babe/epoch_test.go index bb2a2b5b77..0e4d62ccfe 100644 --- a/lib/babe/epoch_test.go +++ b/lib/babe/epoch_test.go @@ -91,8 +91,8 @@ func TestBabeService_getEpochDataAndStartSlot(t *testing.T) { Authorities: []types.Authority{*authority}, } - mockEpochState1.EXPECT().GetEpochData(uint64(1)).Return(testEpochData, nil) - mockEpochState2.EXPECT().GetEpochData(uint64(1)).Return(testEpochData, nil) + mockEpochState1.EXPECT().GetEpochData(uint64(1), nil).Return(testEpochData, nil) + mockEpochState2.EXPECT().GetEpochData(uint64(1), nil).Return(testEpochData, nil) mockEpochState1.EXPECT().HasConfigData(uint64(1)).Return(true, nil) mockEpochState2.EXPECT().HasConfigData(uint64(1)).Return(false, nil) @@ -102,7 +102,7 @@ func TestBabeService_getEpochDataAndStartSlot(t *testing.T) { C2: 1, } - mockEpochState1.EXPECT().GetConfigData(uint64(1)).Return(testConfigData, nil) + mockEpochState1.EXPECT().GetConfigData(uint64(1), nil).Return(testConfigData, nil) testLatestConfigData := &types.ConfigData{ C1: 1, diff --git a/lib/babe/mock_state_test.go b/lib/babe/mock_state_test.go index 2e11a41f36..f5bdfd2d91 100644 --- a/lib/babe/mock_state_test.go +++ b/lib/babe/mock_state_test.go @@ -529,18 +529,18 @@ func (m *MockEpochState) EXPECT() *MockEpochStateMockRecorder { } // GetConfigData mocks base method. -func (m *MockEpochState) GetConfigData(arg0 uint64) (*types.ConfigData, error) { +func (m *MockEpochState) GetConfigData(arg0 uint64, arg1 *types.Header) (*types.ConfigData, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetConfigData", arg0) + ret := m.ctrl.Call(m, "GetConfigData", arg0, arg1) 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 { +func (mr *MockEpochStateMockRecorder) GetConfigData(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConfigData", reflect.TypeOf((*MockEpochState)(nil).GetConfigData), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConfigData", reflect.TypeOf((*MockEpochState)(nil).GetConfigData), arg0, arg1) } // GetCurrentEpoch mocks base method. @@ -559,18 +559,18 @@ func (mr *MockEpochStateMockRecorder) GetCurrentEpoch() *gomock.Call { } // GetEpochData mocks base method. -func (m *MockEpochState) GetEpochData(arg0 uint64) (*types.EpochData, error) { +func (m *MockEpochState) GetEpochData(arg0 uint64, arg1 *types.Header) (*types.EpochData, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetEpochData", arg0) + ret := m.ctrl.Call(m, "GetEpochData", arg0, arg1) 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 { +func (mr *MockEpochStateMockRecorder) GetEpochData(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEpochData", reflect.TypeOf((*MockEpochState)(nil).GetEpochData), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEpochData", reflect.TypeOf((*MockEpochState)(nil).GetEpochData), arg0, arg1) } // GetEpochForBlock mocks base method. diff --git a/lib/babe/state.go b/lib/babe/state.go index 3e59be8aea..5b168e850a 100644 --- a/lib/babe/state.go +++ b/lib/babe/state.go @@ -65,9 +65,12 @@ type EpochState interface { SetCurrentEpoch(epoch uint64) error GetCurrentEpoch() (uint64, error) SetEpochData(uint64, *types.EpochData) error - GetEpochData(epoch uint64) (*types.EpochData, error) + HasEpochData(epoch uint64) (bool, error) - GetConfigData(epoch uint64) (*types.ConfigData, error) + + GetEpochData(epoch uint64, header *types.Header) (*types.EpochData, error) + GetConfigData(epoch uint64, header *types.Header) (*types.ConfigData, error) + HasConfigData(epoch uint64) (bool, error) GetLatestConfigData() (*types.ConfigData, error) GetStartSlotForEpoch(epoch uint64) (uint64, error) diff --git a/lib/babe/verify.go b/lib/babe/verify.go index 459b5e57c9..add58392a4 100644 --- a/lib/babe/verify.go +++ b/lib/babe/verify.go @@ -72,7 +72,7 @@ func (v *VerificationManager) SetOnDisabled(index uint32, header *types.Header) defer v.lock.Unlock() if _, has := v.epochInfo[epoch]; !has { - info, err := v.getVerifierInfo(epoch) + info, err := v.getVerifierInfo(epoch, header) if err != nil { return err } @@ -127,6 +127,7 @@ func (v *VerificationManager) SetOnDisabled(index uint32, header *types.Header) } // VerifyBlock verifies that the block producer for the given block was authorized to produce it. +// It checks the next epoch and config data stored in memory only if it cannot retrieve the data from database // It returns an error if the block is invalid. func (v *VerificationManager) VerifyBlock(header *types.Header) error { var ( @@ -165,7 +166,7 @@ func (v *VerificationManager) VerifyBlock(header *types.Header) error { v.lock.Lock() if info, has = v.epochInfo[epoch]; !has { - info, err = v.getVerifierInfo(epoch) + info, err = v.getVerifierInfo(epoch, header) if err != nil { v.lock.Unlock() // SkipVerify is set to true only in the case where we have imported a state at a given height, @@ -195,13 +196,13 @@ func (v *VerificationManager) VerifyBlock(header *types.Header) error { return verifier.verifyAuthorshipRight(header) } -func (v *VerificationManager) getVerifierInfo(epoch uint64) (*verifierInfo, error) { - epochData, err := v.epochState.GetEpochData(epoch) +func (v *VerificationManager) getVerifierInfo(epoch uint64, header *types.Header) (*verifierInfo, error) { + epochData, err := v.epochState.GetEpochData(epoch, header) if err != nil { return nil, fmt.Errorf("failed to get epoch data for epoch %d: %w", epoch, err) } - configData, err := v.getConfigData(epoch) + configData, err := v.getConfigData(epoch, header) if err != nil { return nil, fmt.Errorf("failed to get config data: %w", err) } @@ -219,16 +220,16 @@ func (v *VerificationManager) getVerifierInfo(epoch uint64) (*verifierInfo, erro }, nil } -func (v *VerificationManager) getConfigData(epoch uint64) (*types.ConfigData, error) { +func (v *VerificationManager) getConfigData(epoch uint64, header *types.Header) (*types.ConfigData, error) { for i := int(epoch); i >= 0; i-- { has, err := v.epochState.HasConfigData(uint64(i)) if err != nil { return nil, err + } else if !has { + continue } - if has { - return v.epochState.GetConfigData(uint64(i)) - } + return v.epochState.GetConfigData(uint64(i), header) } return nil, errNoConfigData diff --git a/lib/babe/verify_integration_test.go b/lib/babe/verify_integration_test.go index 9c1f201aab..48cdd864da 100644 --- a/lib/babe/verify_integration_test.go +++ b/lib/babe/verify_integration_test.go @@ -7,9 +7,14 @@ package babe import ( "errors" + "fmt" "testing" + "time" + "github.com/ChainSafe/chaindb" + "github.com/ChainSafe/gossamer/dot/digest" "github.com/ChainSafe/gossamer/dot/state" + "github.com/ChainSafe/gossamer/dot/telemetry" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/internal/log" "github.com/ChainSafe/gossamer/lib/common" @@ -427,3 +432,296 @@ func TestVerifyAuthorshipRight_Equivocation(t *testing.T) { err = verifier.verifyAuthorshipRight(&block2.Header) require.Equal(t, ErrProducerEquivocated, err) } + +func TestVerifyForkBlocksWithRespectiveEpochData(t *testing.T) { + /* + * Setup the services: StateService, DigestHandler, EpochState + * and VerificationManager + */ + keyPairs := []*sr25519.Keypair{ + keyring.KeyAlice, keyring.KeyBob, keyring.KeyCharlie, + keyring.KeyDave, keyring.KeyEve, keyring.KeyFerdie, + keyring.KeyGeorge, keyring.KeyHeather, keyring.KeyIan, + } + + authorities := make([]types.AuthorityRaw, len(keyPairs)) + for i, keyPair := range keyPairs { + authorities[i] = types.AuthorityRaw{ + Key: keyPair.Public().(*sr25519.PublicKey).AsBytes(), + } + } + + // starts with only 3 authorities in the authority set + epochBABEConfig := &types.BabeConfiguration{ + SlotDuration: 1000, + EpochLength: 10, + C1: 1, + C2: 4, + GenesisAuthorities: authorities[:3], + Randomness: [32]byte{}, + SecondarySlots: 0, + } + + genesis, trie, genesisHeader := genesis.NewTestGenesisWithTrieAndHeader(t) + + ctrl := gomock.NewController(t) + telemetryMock := NewMockClient(ctrl) + telemetryMock.EXPECT().SendMessage( + telemetry.NewNotifyFinalized( + genesisHeader.Hash(), + fmt.Sprint(genesisHeader.Number), + ), + ) + + stateService := state.NewService(state.Config{ + Path: t.TempDir(), + Telemetry: telemetryMock, + }) + + stateService.UseMemDB() + + err := stateService.Initialise(genesis, genesisHeader, trie) + require.NoError(t, err) + + inMemoryDB, err := chaindb.NewBadgerDB(&chaindb.Config{ + InMemory: true, + DataDir: t.TempDir(), + }) + require.NoError(t, err) + + epochState, err := state.NewEpochStateFromGenesis(inMemoryDB, stateService.Block, epochBABEConfig) + require.NoError(t, err) + + digestHandler, err := digest.NewHandler(log.DoNotChange, stateService.Block, epochState, stateService.Grandpa) + require.NoError(t, err) + + digestHandler.Start() + + verificationManager, err := NewVerificationManager(stateService.Block, epochState) + require.NoError(t, err) + + /* + * lets issue different blocks starting from genesis (a fork) + */ + aliceBlockNextEpoch := types.NextEpochData{ + Authorities: authorities[3:], + } + aliceBlockNextConfigData := types.NextConfigData{ + C1: 9, + C2: 10, + SecondarySlots: 1, + } + aliceBlockHeader := issueConsensusDigestsBlockFromGenesis(t, genesisHeader, keyring.KeyAlice, + stateService, aliceBlockNextEpoch, aliceBlockNextConfigData) + + bobBlockNextEpoch := types.NextEpochData{ + Authorities: authorities[6:], + } + bobBlockNextConfigData := types.NextConfigData{ + C1: 3, + C2: 8, + SecondarySlots: 1, + } + bobBlockHeader := issueConsensusDigestsBlockFromGenesis(t, genesisHeader, keyring.KeyBob, + stateService, bobBlockNextEpoch, bobBlockNextConfigData) + + // wait for digest handleBlockImport goroutine gets the imported + // block, process its digest and store the info at epoch state + time.Sleep(time.Second * 2) + + /* + * Simulate a fork from the genesis file, the fork alice and the fork bob + * contains different digest handlers. + */ + const chainLen = 5 + forkAliceChain := make([]types.Header, chainLen) + forkBobChain := make([]types.Header, chainLen) + + forkAliceLastHeader := aliceBlockHeader + forkBobLastHeader := bobBlockHeader + + for idx := 0; idx < chainLen; idx++ { + forkAliceLastHeader = issueNewBlockFrom(t, forkAliceLastHeader, + keyring.KeyAlice, stateService) + + forkBobLastHeader = issueNewBlockFrom(t, forkBobLastHeader, + keyring.KeyBob, stateService) + + forkAliceChain[idx] = *forkAliceLastHeader + forkBobChain[idx] = *forkBobLastHeader + } + + // verify if each block from the fork alice get the right digest + const epochToTest = 1 + + expectedThreshold, err := CalculateThreshold(aliceBlockNextConfigData.C1, + aliceBlockNextConfigData.C2, len(authorities[3:])) + require.NoError(t, err) + + for _, headerToVerify := range forkAliceChain { + verifierInfo, err := verificationManager.getVerifierInfo(epochToTest, &headerToVerify) + require.NoError(t, err) + + require.Equal(t, len(authorities[3:]), len(verifierInfo.authorities)) + rawAuthorities := make([]types.AuthorityRaw, len(verifierInfo.authorities)) + + for i, auth := range verifierInfo.authorities { + rawAuthorities[i] = *auth.ToRaw() + } + + require.ElementsMatch(t, authorities[3:], rawAuthorities) + require.True(t, verifierInfo.secondarySlots) + require.Equal(t, expectedThreshold, verifierInfo.threshold) + } + + // each block from the fork bob should use the right digest + + expectedThreshold, err = CalculateThreshold(bobBlockNextConfigData.C1, + bobBlockNextConfigData.C2, len(authorities[6:])) + require.NoError(t, err) + + for _, headerToVerify := range forkBobChain { + verifierInfo, err := verificationManager.getVerifierInfo(epochToTest, &headerToVerify) + require.NoError(t, err) + + require.Equal(t, len(authorities[6:]), len(verifierInfo.authorities)) + rawAuthorities := make([]types.AuthorityRaw, len(verifierInfo.authorities)) + + for i, auth := range verifierInfo.authorities { + rawAuthorities[i] = *auth.ToRaw() + } + + // should keep the original authorities + require.ElementsMatch(t, authorities[6:], rawAuthorities) + require.True(t, verifierInfo.secondarySlots) + require.Equal(t, expectedThreshold, verifierInfo.threshold) + } + + telemetryMock.EXPECT().SendMessage( + telemetry.NewNotifyFinalized( + forkBobLastHeader.Hash(), + fmt.Sprint(forkBobLastHeader.Number), + ), + ) + err = stateService.Block.SetFinalisedHash(forkBobLastHeader.Hash(), 1, 1) + require.NoError(t, err) + + // wait for digest handleBlockFinalize goroutine gets the finalized + // block, clean up the in memory data and store the finalized digest in db + time.Sleep(time.Second * 2) + + // as a chain was finalized any block built upon it should use the database stored data + blockUponFinalizedHeader := issueNewBlockFrom(t, forkBobLastHeader, + keyring.KeyBob, stateService) + + verifierInfo, err := verificationManager.getVerifierInfo(epochToTest, blockUponFinalizedHeader) + require.NoError(t, err) + + require.Equal(t, len(authorities[6:]), len(verifierInfo.authorities)) + rawAuthorities := make([]types.AuthorityRaw, len(verifierInfo.authorities)) + + for i, auth := range verifierInfo.authorities { + rawAuthorities[i] = *auth.ToRaw() + } + + // should keep the original authorities + require.ElementsMatch(t, authorities[6:], rawAuthorities) + require.True(t, verifierInfo.secondarySlots) + require.Equal(t, expectedThreshold, verifierInfo.threshold) +} + +// issueConsensusDigestsBlocksFromGenesis will create different +// blocks that contains different consensus messages digests +func issueConsensusDigestsBlockFromGenesis(t *testing.T, genesisHeader *types.Header, + kp *sr25519.Keypair, stateService *state.Service, + nextEpoch types.NextEpochData, nextConfig types.NextConfigData) *types.Header { + t.Helper() + + output, proof, err := kp.VrfSign(makeTranscript(Randomness{}, uint64(0), 0)) + require.NoError(t, err) + + babePrimaryPreDigest := types.BabePrimaryPreDigest{ + SlotNumber: 1, + VRFOutput: output, + VRFProof: proof, + } + + preRuntimeDigest, err := babePrimaryPreDigest.ToPreRuntimeDigest() + require.NoError(t, err) + + babeConsensusDigestNextEpoch := types.NewBabeConsensusDigest() + require.NoError(t, babeConsensusDigestNextEpoch.Set(nextEpoch)) + + babeConsensusDigestNextConfigData := types.NewBabeConsensusDigest() + require.NoError(t, babeConsensusDigestNextConfigData.Set(nextConfig)) + + nextEpochData, err := scale.Marshal(babeConsensusDigestNextEpoch) + require.NoError(t, err) + + nextEpochConsensusDigest := types.ConsensusDigest{ + ConsensusEngineID: types.BabeEngineID, + Data: nextEpochData, + } + + nextConfigData, err := scale.Marshal(babeConsensusDigestNextConfigData) + require.NoError(t, err) + + nextConfigConsensusDigest := types.ConsensusDigest{ + ConsensusEngineID: types.BabeEngineID, + Data: nextConfigData, + } + + digest := types.NewDigest() + require.NoError(t, digest.Add(*preRuntimeDigest, nextEpochConsensusDigest, nextConfigConsensusDigest)) + + headerWhoOwnsNextEpochDigest := &types.Header{ + ParentHash: genesisHeader.Hash(), + Number: 1, + Digest: digest, + } + + err = stateService.Block.AddBlock(&types.Block{ + Header: *headerWhoOwnsNextEpochDigest, + Body: *types.NewBody([]types.Extrinsic{}), + }) + require.NoError(t, err) + + return headerWhoOwnsNextEpochDigest +} + +// issueNewBlockFrom will create and store a new block following a chain +func issueNewBlockFrom(t *testing.T, parentHeader *types.Header, + kp *sr25519.Keypair, stateService *state.Service) *types.Header { + t.Helper() + + output, proof, err := kp.VrfSign(makeTranscript(Randomness{}, uint64(1), 1)) + require.NoError(t, err) + + babePrimaryPreDigest := types.BabePrimaryPreDigest{ + SlotNumber: 1, + VRFOutput: output, + VRFProof: proof, + } + + preRuntimeDigest, err := babePrimaryPreDigest.ToPreRuntimeDigest() + require.NoError(t, err) + + digest := scale.NewVaryingDataTypeSlice(scale.MustNewVaryingDataType( + types.PreRuntimeDigest{})) + + require.NoError(t, digest.Add(*preRuntimeDigest)) + + header := &types.Header{ + ParentHash: parentHeader.Hash(), + Number: parentHeader.Number + 1, + Digest: digest, + } + + err = stateService.Block.AddBlock(&types.Block{ + Header: *header, + Body: *types.NewBody([]types.Extrinsic{}), + }) + require.NoError(t, err) + + return header +} diff --git a/lib/babe/verify_test.go b/lib/babe/verify_test.go index 40cb479883..f6c106ca7c 100644 --- a/lib/babe/verify_test.go +++ b/lib/babe/verify_test.go @@ -737,10 +737,12 @@ func TestVerificationManager_getConfigData(t *testing.T) { mockEpochStateHasErr := NewMockEpochState(ctrl) mockEpochStateGetErr := NewMockEpochState(ctrl) + testHeader := types.NewEmptyHeader() + mockEpochStateEmpty.EXPECT().HasConfigData(uint64(0)).Return(false, nil) mockEpochStateHasErr.EXPECT().HasConfigData(uint64(0)).Return(false, errNoConfigData) mockEpochStateGetErr.EXPECT().HasConfigData(uint64(0)).Return(true, nil) - mockEpochStateGetErr.EXPECT().GetConfigData(uint64(0)).Return(nil, errNoConfigData) + mockEpochStateGetErr.EXPECT().GetConfigData(uint64(0), testHeader).Return(nil, errNoConfigData) vm0, err := NewVerificationManager(mockBlockState, mockEpochStateEmpty) assert.NoError(t, err) @@ -774,7 +776,7 @@ func TestVerificationManager_getConfigData(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { v := tt.vm - res, err := v.getConfigData(tt.epoch) + res, err := v.getConfigData(tt.epoch, testHeader) if tt.expErr != nil { assert.EqualError(t, err, tt.expErr.Error()) } else { @@ -793,22 +795,24 @@ func TestVerificationManager_getVerifierInfo(t *testing.T) { mockEpochStateThresholdErr := NewMockEpochState(ctrl) mockEpochStateOk := NewMockEpochState(ctrl) - mockEpochStateGetErr.EXPECT().GetEpochData(uint64(0)).Return(nil, errNoConfigData) + testHeader := types.NewEmptyHeader() + + mockEpochStateGetErr.EXPECT().GetEpochData(uint64(0), testHeader).Return(nil, errNoConfigData) - mockEpochStateHasErr.EXPECT().GetEpochData(uint64(0)).Return(&types.EpochData{}, nil) + mockEpochStateHasErr.EXPECT().GetEpochData(uint64(0), testHeader).Return(&types.EpochData{}, nil) mockEpochStateHasErr.EXPECT().HasConfigData(uint64(0)).Return(false, errNoConfigData) - mockEpochStateThresholdErr.EXPECT().GetEpochData(uint64(0)).Return(&types.EpochData{}, nil) + mockEpochStateThresholdErr.EXPECT().GetEpochData(uint64(0), testHeader).Return(&types.EpochData{}, nil) mockEpochStateThresholdErr.EXPECT().HasConfigData(uint64(0)).Return(true, nil) - mockEpochStateThresholdErr.EXPECT().GetConfigData(uint64(0)). + mockEpochStateThresholdErr.EXPECT().GetConfigData(uint64(0), testHeader). Return(&types.ConfigData{ C1: 3, C2: 1, }, nil) - mockEpochStateOk.EXPECT().GetEpochData(uint64(0)).Return(&types.EpochData{}, nil) + mockEpochStateOk.EXPECT().GetEpochData(uint64(0), testHeader).Return(&types.EpochData{}, nil) mockEpochStateOk.EXPECT().HasConfigData(uint64(0)).Return(true, nil) - mockEpochStateOk.EXPECT().GetConfigData(uint64(0)). + mockEpochStateOk.EXPECT().GetConfigData(uint64(0), testHeader). Return(&types.ConfigData{ C1: 1, C2: 3, @@ -856,7 +860,7 @@ func TestVerificationManager_getVerifierInfo(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { v := tt.vm - res, err := v.getVerifierInfo(tt.epoch) + res, err := v.getVerifierInfo(tt.epoch, testHeader) if tt.expErr != nil { assert.EqualError(t, err, tt.expErr.Error()) } else { @@ -905,15 +909,15 @@ func TestVerificationManager_VerifyBlock(t *testing.T) { Return(uint64(0), errGetEpoch) mockEpochStateSkipVerifyErr.EXPECT().GetEpochForBlock(testBlockHeaderEmpty).Return(uint64(1), nil) - mockEpochStateSkipVerifyErr.EXPECT().GetEpochData(uint64(1)).Return(nil, errGetEpochData) + mockEpochStateSkipVerifyErr.EXPECT().GetEpochData(uint64(1), testBlockHeaderEmpty).Return(nil, errGetEpochData) mockEpochStateSkipVerifyErr.EXPECT().SkipVerify(testBlockHeaderEmpty).Return(false, errSkipVerify) mockEpochStateSkipVerifyTrue.EXPECT().GetEpochForBlock(testBlockHeaderEmpty).Return(uint64(1), nil) - mockEpochStateSkipVerifyTrue.EXPECT().GetEpochData(uint64(1)).Return(nil, errGetEpochData) + mockEpochStateSkipVerifyTrue.EXPECT().GetEpochData(uint64(1), testBlockHeaderEmpty).Return(nil, errGetEpochData) mockEpochStateSkipVerifyTrue.EXPECT().SkipVerify(testBlockHeaderEmpty).Return(true, nil) mockEpochStateGetVerifierInfoErr.EXPECT().GetEpochForBlock(testBlockHeaderEmpty).Return(uint64(1), nil) - mockEpochStateGetVerifierInfoErr.EXPECT().GetEpochData(uint64(1)). + mockEpochStateGetVerifierInfoErr.EXPECT().GetEpochData(uint64(1), testBlockHeaderEmpty). Return(nil, errGetEpochData) mockEpochStateGetVerifierInfoErr.EXPECT().SkipVerify(testBlockHeaderEmpty).Return(false, nil) @@ -1065,7 +1069,7 @@ func TestVerificationManager_SetOnDisabled(t *testing.T) { mockEpochStateGetEpochErr.EXPECT().GetEpochForBlock(types.NewEmptyHeader()).Return(uint64(0), errGetEpoch) mockEpochStateGetEpochDataErr.EXPECT().GetEpochForBlock(types.NewEmptyHeader()).Return(uint64(0), nil) - mockEpochStateGetEpochDataErr.EXPECT().GetEpochData(uint64(0)).Return(nil, errGetEpochData) + mockEpochStateGetEpochDataErr.EXPECT().GetEpochData(uint64(0), types.NewEmptyHeader()).Return(nil, errGetEpochData) mockEpochStateIndexLenErr.EXPECT().GetEpochForBlock(types.NewEmptyHeader()).Return(uint64(2), nil) diff --git a/lib/grandpa/grandpa_test.go b/lib/grandpa/grandpa_test.go index da73202e8e..4aaefed652 100644 --- a/lib/grandpa/grandpa_test.go +++ b/lib/grandpa/grandpa_test.go @@ -133,7 +133,7 @@ func TestUpdateAuthorities(t *testing.T) { err = gs.grandpaState.(*state.GrandpaState).SetNextChange(next, 1) require.NoError(t, err) - err = gs.grandpaState.(*state.GrandpaState).IncrementSetID() + _, err = gs.grandpaState.(*state.GrandpaState).IncrementSetID() require.NoError(t, err) err = gs.updateAuthorities() diff --git a/lib/grandpa/message_handler_test.go b/lib/grandpa/message_handler_test.go index 54973774a6..5e30d15a92 100644 --- a/lib/grandpa/message_handler_test.go +++ b/lib/grandpa/message_handler_test.go @@ -619,10 +619,7 @@ func TestMessageHandler_VerifyBlockJustification_WithEquivocatoryVotes(t *testin err = st.Block.AddBlock(block) require.NoError(t, err) - err = st.Grandpa.IncrementSetID() - require.NoError(t, err) - - setID, err := st.Grandpa.GetCurrentSetID() + setID, err := st.Grandpa.IncrementSetID() require.NoError(t, err) require.Equal(t, uint64(1), setID) @@ -664,10 +661,7 @@ func TestMessageHandler_VerifyBlockJustification(t *testing.T) { err = st.Block.AddBlock(block) require.NoError(t, err) - err = st.Grandpa.IncrementSetID() - require.NoError(t, err) - - setID, err := st.Grandpa.GetCurrentSetID() + setID, err := st.Grandpa.IncrementSetID() require.NoError(t, err) require.Equal(t, uint64(1), setID) @@ -721,10 +715,7 @@ func TestMessageHandler_VerifyBlockJustification_invalid(t *testing.T) { err = st.Block.AddBlock(block) require.NoError(t, err) - err = st.Grandpa.IncrementSetID() - require.NoError(t, err) - - setID, err := st.Grandpa.GetCurrentSetID() + setID, err := st.Grandpa.IncrementSetID() require.NoError(t, err) require.Equal(t, uint64(1), setID)