diff --git a/cmd/gossamer/commands/import_state.go b/cmd/gossamer/commands/import_state.go index c83f267cbf..80ab81ad0d 100644 --- a/cmd/gossamer/commands/import_state.go +++ b/cmd/gossamer/commands/import_state.go @@ -79,5 +79,6 @@ func execImportState(cmd *cobra.Command) error { basePath = utils.ExpandDir(basePath) - return dot.ImportState(basePath, stateFile, headerFile, stateTrieVersion, firstSlot) + return dot.ImportState(basePath, stateFile, + headerFile, stateTrieVersion, nil, firstSlot) } diff --git a/dot/build_spec.go b/dot/build_spec.go index fd377da69c..e298d57e62 100644 --- a/dot/build_spec.go +++ b/dot/build_spec.go @@ -11,6 +11,7 @@ import ( "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" "github.com/ChainSafe/gossamer/lib/genesis" @@ -98,10 +99,13 @@ func BuildFromDB(path string) (*BuildSpec, error) { Path: path, LogLevel: log.Info, Telemetry: telemetry.NewNoopMailer(), + GenesisBABEConfig: &types.BabeConfiguration{ + EpochLength: 10, + SlotDuration: 6, + }, } stateSrvc := state.NewService(config) - err := stateSrvc.SetupBase() if err != nil { return nil, fmt.Errorf("cannot setup state database: %w", err) diff --git a/dot/core/helpers_test.go b/dot/core/helpers_test.go index 9c8a49ddd6..8dc8bb12c1 100644 --- a/dot/core/helpers_test.go +++ b/dot/core/helpers_test.go @@ -23,6 +23,7 @@ import ( "github.com/ChainSafe/gossamer/lib/utils" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/ChainSafe/gossamer/pkg/trie" + "github.com/ChainSafe/gossamer/tests/utils/config" "github.com/centrifuge/go-substrate-rpc-client/v4/signature" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" @@ -76,9 +77,10 @@ func createTestService(t *testing.T, genesisFilePath string, telemetryMock.EXPECT().SendMessage(gomock.Any()).AnyTimes() stateConfig := state.Config{ - Path: testDatadirPath, - LogLevel: log.Critical, - Telemetry: telemetryMock, + Path: testDatadirPath, + LogLevel: log.Critical, + Telemetry: telemetryMock, + GenesisBABEConfig: config.BABEConfigurationTestDefault, } stateSrvc = state.NewService(stateConfig) @@ -181,9 +183,10 @@ func NewTestService(t *testing.T, cfg *Config) *Service { telemetryMock.EXPECT().SendMessage(gomock.Any()).AnyTimes() config := state.Config{ - Path: testDatadirPath, - LogLevel: log.Info, - Telemetry: telemetryMock, + Path: testDatadirPath, + LogLevel: log.Info, + Telemetry: telemetryMock, + GenesisBABEConfig: config.BABEConfigurationTestDefault, } stateSrvc = state.NewService(config) diff --git a/dot/digest/digest_integration_test.go b/dot/digest/digest_integration_test.go index a309e5e82b..c63ce25517 100644 --- a/dot/digest/digest_integration_test.go +++ b/dot/digest/digest_integration_test.go @@ -18,6 +18,7 @@ import ( "github.com/ChainSafe/gossamer/lib/crypto/sr25519" "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/pkg/scale" + "github.com/ChainSafe/gossamer/tests/utils/config" "go.uber.org/mock/gomock" "github.com/stretchr/testify/require" @@ -31,8 +32,9 @@ func newTestHandler(t *testing.T) (*Handler, *BlockImportHandler, *state.Service telemetryMock.EXPECT().SendMessage(gomock.Any()).AnyTimes() config := state.Config{ - Path: testDatadirPath, - Telemetry: telemetryMock, + Path: testDatadirPath, + Telemetry: telemetryMock, + GenesisBABEConfig: config.BABEConfigurationTestDefault, } stateSrvc := state.NewService(config) stateSrvc.UseMemDB() diff --git a/dot/import.go b/dot/import.go index 69257aa415..5ca6f11e08 100644 --- a/dot/import.go +++ b/dot/import.go @@ -21,7 +21,8 @@ import ( ) // ImportState imports the state in the given files to the database with the given path. -func ImportState(basepath, stateFP, headerFP string, stateTrieVersion trie.TrieLayout, firstSlot uint64) error { +func ImportState(basepath, stateFP, headerFP string, stateTrieVersion trie.TrieLayout, + genesisBABEConfig *types.BabeConfiguration, firstSlot uint64) error { tr, err := newTrieFromPairs(stateFP, trie.V0) if err != nil { return err @@ -35,8 +36,9 @@ func ImportState(basepath, stateFP, headerFP string, stateTrieVersion trie.TrieL logger.Infof("ImportState with header: %v", header) config := state.Config{ - Path: basepath, - LogLevel: log.Info, + Path: basepath, + LogLevel: log.Info, + GenesisBABEConfig: genesisBABEConfig, } srv := state.NewService(config) return srv.Import(header, tr, stateTrieVersion, firstSlot) diff --git a/dot/import_integration_test.go b/dot/import_integration_test.go index 291506b47d..f389eecdf6 100644 --- a/dot/import_integration_test.go +++ b/dot/import_integration_test.go @@ -15,6 +15,7 @@ import ( "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/ChainSafe/gossamer/pkg/trie" + "github.com/ChainSafe/gossamer/tests/utils/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -90,24 +91,25 @@ func TestNewHeaderFromFile(t *testing.T) { } func TestImportState_Integration(t *testing.T) { - config := DefaultTestWestendDevConfig(t) + defaultWestendDevConfig := DefaultTestWestendDevConfig(t) - genFile := NewTestGenesisRawFile(t, config) - - config.ChainSpec = genFile - err := InitNode(config) + genFile := NewTestGenesisRawFile(t, defaultWestendDevConfig) + defaultWestendDevConfig.ChainSpec = genFile + err := InitNode(defaultWestendDevConfig) require.NoError(t, err) stateFP := setupStateFile(t) headerFP := setupHeaderFile(t) - const firstSlot = uint64(262493679) - err = ImportState(config.BasePath, stateFP, headerFP, trie.V0, firstSlot) + firstSlot := uint64(1) + err = ImportState(defaultWestendDevConfig.BasePath, stateFP, headerFP, + trie.V0, config.BABEConfigurationTestDefault, firstSlot) require.NoError(t, err) // confirm data is imported into db stateConfig := state.Config{ - Path: config.BasePath, - LogLevel: log.Info, + Path: defaultWestendDevConfig.BasePath, + LogLevel: log.Info, + GenesisBABEConfig: config.BABEConfigurationTestDefault, } srv := state.NewService(stateConfig) srv.SetupBase() @@ -122,11 +124,11 @@ func TestImportState_Integration(t *testing.T) { func TestImportState(t *testing.T) { t.Parallel() - config := DefaultTestWestendDevConfig(t) + defaultWestendDevConfig := DefaultTestWestendDevConfig(t) - config.ChainSpec = NewTestGenesisRawFile(t, config) + defaultWestendDevConfig.ChainSpec = NewTestGenesisRawFile(t, defaultWestendDevConfig) nodeInstance := nodeBuilder{} - err := nodeInstance.initNode(config) + err := nodeInstance.initNode(defaultWestendDevConfig) require.NoError(t, err) stateFP := setupStateFile(t) @@ -151,7 +153,7 @@ func TestImportState(t *testing.T) { { name: "working_example", args: args{ - basepath: config.BasePath, + basepath: defaultWestendDevConfig.BasePath, stateFP: stateFP, headerFP: headerFP, stateVersion: trie.V0, @@ -164,7 +166,8 @@ func TestImportState(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - err := ImportState(tt.args.basepath, tt.args.stateFP, tt.args.headerFP, tt.args.stateVersion, tt.args.firstSlot) + err := ImportState(tt.args.basepath, tt.args.stateFP, + tt.args.headerFP, tt.args.stateVersion, config.BABEConfigurationTestDefault, tt.args.firstSlot) if tt.err != nil { assert.EqualError(t, err, tt.err.Error()) } else { diff --git a/dot/node_integration_test.go b/dot/node_integration_test.go index 441b1b34e8..ad1bf47536 100644 --- a/dot/node_integration_test.go +++ b/dot/node_integration_test.go @@ -15,6 +15,7 @@ import ( "testing" "github.com/ChainSafe/gossamer/chain/westend" + "github.com/ChainSafe/gossamer/tests/utils/config" cfg "github.com/ChainSafe/gossamer/config" "github.com/ChainSafe/gossamer/dot/core" @@ -73,8 +74,9 @@ func TestNewNode(t *testing.T) { logLevel, err := log.ParseLevel(initConfig.Log.State) require.NoError(t, err) stateConfig := state.Config{ - Path: initConfig.BasePath, - LogLevel: logLevel, + Path: initConfig.BasePath, + LogLevel: logLevel, + GenesisBABEConfig: config.BABEConfigurationTestDefault, } systemInfo := &types.SystemInfo{ diff --git a/dot/rpc/http_test.go b/dot/rpc/http_test.go index 160a3c3346..cb5dd5ed85 100644 --- a/dot/rpc/http_test.go +++ b/dot/rpc/http_test.go @@ -15,6 +15,7 @@ import ( rtstorage "github.com/ChainSafe/gossamer/lib/runtime/storage" wazero_runtime "github.com/ChainSafe/gossamer/lib/runtime/wazero" + "github.com/ChainSafe/gossamer/tests/utils/config" "github.com/libp2p/go-libp2p/core/peer" "github.com/ChainSafe/gossamer/dot/core" @@ -308,9 +309,10 @@ func newCoreServiceTest(t *testing.T) *core.Service { telemetryMock.EXPECT().SendMessage(gomock.Any()).AnyTimes() config := state.Config{ - Path: testDatadirPath, - LogLevel: log.Debug, - Telemetry: telemetryMock, + Path: testDatadirPath, + LogLevel: log.Debug, + Telemetry: telemetryMock, + GenesisBABEConfig: config.BABEConfigurationTestDefault, } stateSrvc := state.NewService(config) diff --git a/dot/rpc/modules/author_integration_test.go b/dot/rpc/modules/author_integration_test.go index f52f3a6cf1..016cfb93c5 100644 --- a/dot/rpc/modules/author_integration_test.go +++ b/dot/rpc/modules/author_integration_test.go @@ -33,6 +33,7 @@ import ( "github.com/ChainSafe/gossamer/lib/transaction" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/ChainSafe/gossamer/pkg/trie" + "github.com/ChainSafe/gossamer/tests/utils/config" cscale "github.com/centrifuge/go-substrate-rpc-client/v4/scale" "github.com/centrifuge/go-substrate-rpc-client/v4/signature" ctypes "github.com/centrifuge/go-substrate-rpc-client/v4/types" @@ -661,9 +662,10 @@ func setupStateAndRuntime(t *testing.T, basepath string, useInstance useRuntimeI ) state2test := state.NewService(state.Config{ - LogLevel: log.DoNotChange, - Path: basepath, - Telemetry: telemetryMock, + LogLevel: log.DoNotChange, + Path: basepath, + Telemetry: telemetryMock, + GenesisBABEConfig: config.BABEConfigurationTestDefault, }) state2test.UseMemDB() @@ -721,9 +723,10 @@ func setupStateAndPopulateTrieState(t *testing.T, basepath string, ) state2test := state.NewService(state.Config{ - LogLevel: log.DoNotChange, - Path: basepath, - Telemetry: telemetryMock, + LogLevel: log.DoNotChange, + Path: basepath, + Telemetry: telemetryMock, + GenesisBABEConfig: config.BABEConfigurationTestDefault, }) state2test.UseMemDB() diff --git a/dot/rpc/modules/chain_integration_test.go b/dot/rpc/modules/chain_integration_test.go index 2bfa8a2eb7..6bbbf741d8 100644 --- a/dot/rpc/modules/chain_integration_test.go +++ b/dot/rpc/modules/chain_integration_test.go @@ -18,6 +18,7 @@ import ( wazero_runtime "github.com/ChainSafe/gossamer/lib/runtime/wazero" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/ChainSafe/gossamer/pkg/trie" + "github.com/ChainSafe/gossamer/tests/utils/config" "go.uber.org/mock/gomock" rtstorage "github.com/ChainSafe/gossamer/lib/runtime/storage" @@ -344,9 +345,10 @@ func newTestStateService(t *testing.T) *state.Service { telemetryMock.EXPECT().SendMessage(gomock.Any()).AnyTimes() config := state.Config{ - Path: testDatadirPath, - LogLevel: log.Info, - Telemetry: telemetryMock, + Path: testDatadirPath, + LogLevel: log.Info, + Telemetry: telemetryMock, + GenesisBABEConfig: config.BABEConfigurationTestDefault, } stateSrvc := state.NewService(config) stateSrvc.UseMemDB() diff --git a/dot/rpc/modules/dev_integration_test.go b/dot/rpc/modules/dev_integration_test.go index 1a3fcaaf43..1de719eabd 100644 --- a/dot/rpc/modules/dev_integration_test.go +++ b/dot/rpc/modules/dev_integration_test.go @@ -10,7 +10,6 @@ import ( "testing" "github.com/ChainSafe/gossamer/dot/state" - "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/babe" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/crypto/sr25519" @@ -18,20 +17,11 @@ import ( "github.com/ChainSafe/gossamer/lib/runtime" wazero_runtime "github.com/ChainSafe/gossamer/lib/runtime/wazero" inmemory_trie "github.com/ChainSafe/gossamer/pkg/trie/inmemory" + "github.com/ChainSafe/gossamer/tests/utils/config" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" ) -var genesisBABEConfig = &types.BabeConfiguration{ - SlotDuration: 1000, - EpochLength: 200, - C1: 1, - C2: 4, - GenesisAuthorities: []types.AuthorityRaw{}, - Randomness: [32]byte{}, - SecondarySlots: 0, -} - func newState(t *testing.T) (*state.BlockState, *state.EpochState) { ctrl := gomock.NewController(t) telemetryMock := NewMockTelemetry(ctrl) @@ -44,7 +34,7 @@ func newState(t *testing.T) (*state.BlockState, *state.EpochState) { tries.SetTrie(genesisTrie) bs, err := state.NewBlockStateFromGenesis(db, tries, &genesisHeader, telemetryMock) require.NoError(t, err) - es, err := state.NewEpochStateFromGenesis(db, bs, genesisBABEConfig) + es, err := state.NewEpochStateFromGenesis(db, bs, config.BABEConfigurationTestDefault) require.NoError(t, err) return bs, es } diff --git a/dot/services.go b/dot/services.go index c6200f86a8..52f4e71872 100644 --- a/dot/services.go +++ b/dot/services.go @@ -29,9 +29,11 @@ import ( "github.com/ChainSafe/gossamer/lib/crypto" "github.com/ChainSafe/gossamer/lib/crypto/ed25519" "github.com/ChainSafe/gossamer/lib/crypto/sr25519" + "github.com/ChainSafe/gossamer/lib/genesis" "github.com/ChainSafe/gossamer/lib/grandpa" "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/lib/runtime" + rtstorage "github.com/ChainSafe/gossamer/lib/runtime/storage" wazero_runtime "github.com/ChainSafe/gossamer/lib/runtime/wazero" ) @@ -63,14 +65,47 @@ func newInMemoryDB() (database.Database, error) { func (nodeBuilder) createStateService(config *cfg.Config) (*state.Service, error) { logger.Debug("creating state service...") + gen, err := genesis.NewGenesisFromJSONRaw(config.ChainSpec) + if err != nil { + return nil, fmt.Errorf("genesis from json raw: %w", err) + } + + if !gen.IsRaw() { + return nil, fmt.Errorf("genesis should be raw") + } + + genTrie, err := runtime.NewTrieFromGenesis(*gen) + if err != nil { + return nil, fmt.Errorf("creating trie from genesis: %w", err) + } + + // create genesis runtime + rtCfg := wazero_runtime.Config{ + LogLvl: log.Critical, + Storage: rtstorage.NewTrieState(genTrie), + } + + genesisRuntime, err := wazero_runtime.NewRuntimeFromGenesis(rtCfg) + if err != nil { + return nil, fmt.Errorf("instantiating genesis runtime: %w", err) + } + defer genesisRuntime.Stop() + + babeCfg, err := genesisRuntime.BabeConfiguration() + if err != nil { + return nil, fmt.Errorf("getting babe configuration: %w", err) + } + stateLogLevel, err := log.ParseLevel(config.Log.State) if err != nil { return nil, err } + stateConfig := state.Config{ - Path: config.BasePath, - LogLevel: stateLogLevel, - Metrics: metrics.NewIntervalConfig(config.PrometheusExternal), + Path: config.BasePath, + LogLevel: stateLogLevel, + Metrics: metrics.NewIntervalConfig(config.PrometheusExternal), + GenesisBABEConfig: babeCfg, } stateSrvc := state.NewService(stateConfig) diff --git a/dot/services_integration_test.go b/dot/services_integration_test.go index a571c1a8ee..6b7261f52a 100644 --- a/dot/services_integration_test.go +++ b/dot/services_integration_test.go @@ -11,6 +11,7 @@ import ( "time" cfg "github.com/ChainSafe/gossamer/config" + "github.com/ChainSafe/gossamer/tests/utils/config" core "github.com/ChainSafe/gossamer/dot/core" "github.com/ChainSafe/gossamer/dot/network" @@ -440,9 +441,10 @@ func newStateServiceWithoutMock(t *testing.T) *state.Service { t.Helper() stateConfig := state.Config{ - Path: t.TempDir(), - LogLevel: log.Error, - Telemetry: telemetry.NoopClient{}, + Path: t.TempDir(), + LogLevel: log.Error, + Telemetry: telemetry.NoopClient{}, + GenesisBABEConfig: config.BABEConfigurationTestDefault, } stateSrvc := state.NewService(stateConfig) stateSrvc.UseMemDB() @@ -453,16 +455,8 @@ func newStateServiceWithoutMock(t *testing.T) *state.Service { err = stateSrvc.SetupBase() require.NoError(t, err) - genesisBABEConfig := &types.BabeConfiguration{ - SlotDuration: 1000, - EpochLength: 200, - C1: 1, - C2: 4, - GenesisAuthorities: []types.AuthorityRaw{}, - Randomness: [32]byte{}, - SecondarySlots: 0, - } - epochState, err := state.NewEpochStateFromGenesis(stateSrvc.DB(), stateSrvc.Block, genesisBABEConfig) + epochState, err := state.NewEpochStateFromGenesis(stateSrvc.DB(), stateSrvc.Block, + config.BABEConfigurationTestDefault) require.NoError(t, err) stateSrvc.Epoch = epochState diff --git a/dot/services_test.go b/dot/services_test.go index 0a6c42354e..d33032aff2 100644 --- a/dot/services_test.go +++ b/dot/services_test.go @@ -12,6 +12,7 @@ import ( "github.com/ChainSafe/gossamer/lib/runtime" rtstorage "github.com/ChainSafe/gossamer/lib/runtime/storage" wazero_runtime "github.com/ChainSafe/gossamer/lib/runtime/wazero" + "github.com/ChainSafe/gossamer/tests/utils/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" @@ -129,9 +130,10 @@ func newStateService(t *testing.T, ctrl *gomock.Controller) *state.Service { telemetryMock.EXPECT().SendMessage(gomock.Any()).AnyTimes() stateConfig := state.Config{ - Path: t.TempDir(), - LogLevel: log.Info, - Telemetry: telemetryMock, + Path: t.TempDir(), + LogLevel: log.Info, + Telemetry: telemetryMock, + GenesisBABEConfig: config.BABEConfigurationTestDefault, } stateSrvc := state.NewService(stateConfig) stateSrvc.UseMemDB() @@ -141,17 +143,8 @@ func newStateService(t *testing.T, ctrl *gomock.Controller) *state.Service { err = stateSrvc.SetupBase() require.NoError(t, err) - - genesisBABEConfig := &types.BabeConfiguration{ - SlotDuration: 1000, - EpochLength: 200, - C1: 1, - C2: 4, - GenesisAuthorities: []types.AuthorityRaw{}, - Randomness: [32]byte{}, - SecondarySlots: 0, - } - epochState, err := state.NewEpochStateFromGenesis(stateSrvc.DB(), stateSrvc.Block, genesisBABEConfig) + epochState, err := state.NewEpochStateFromGenesis(stateSrvc.DB(), stateSrvc.Block, + config.BABEConfigurationTestDefault) require.NoError(t, err) stateSrvc.Epoch = epochState diff --git a/dot/state/base.go b/dot/state/base.go index b146f642a3..0e42fca46c 100644 --- a/dot/state/base.go +++ b/dot/state/base.go @@ -109,48 +109,3 @@ func (s *BaseState) loadSkipToEpoch() (uint64, error) { return binary.LittleEndian.Uint64(data), nil } - -func (s *BaseState) storeFirstSlot(slot uint64) error { - buf := make([]byte, 8) - binary.LittleEndian.PutUint64(buf, slot) - return s.db.Put(firstSlotKey, buf) -} - -func (s *BaseState) loadFirstSlot() (uint64, error) { - data, err := s.db.Get(firstSlotKey) - if err != nil { - return 0, err - } - - return binary.LittleEndian.Uint64(data), nil -} - -func (s *BaseState) storeEpochLength(l uint64) error { - buf := make([]byte, 8) - binary.LittleEndian.PutUint64(buf, l) - return s.db.Put(epochLengthKey, buf) -} - -func (s *BaseState) loadEpochLength() (uint64, error) { - data, err := s.db.Get(epochLengthKey) - if err != nil { - return 0, err - } - - return binary.LittleEndian.Uint64(data), nil -} - -func (s *BaseState) storeSlotDuration(duration uint64) error { - buf := make([]byte, 8) - binary.LittleEndian.PutUint64(buf, duration) - return s.db.Put(slotDurationKey, buf) -} - -func (s *BaseState) loadSlotDuration() (uint64, error) { - data, err := s.db.Get(slotDurationKey) - if err != nil { - return 0, err - } - - return binary.LittleEndian.Uint64(data), nil -} diff --git a/dot/state/base_test.go b/dot/state/base_test.go index b2fd3ae437..58a02e0d0b 100644 --- a/dot/state/base_test.go +++ b/dot/state/base_test.go @@ -63,29 +63,3 @@ func TestStoreAndLoadGenesisData(t *testing.T) { require.NoError(t, err) require.Equal(t, expected, gen) } - -func TestLoadStoreEpochLength(t *testing.T) { - db := NewInMemoryDB(t) - base := NewBaseState(db) - - length := uint64(2222) - err := base.storeEpochLength(length) - require.NoError(t, err) - - ret, err := base.loadEpochLength() - require.NoError(t, err) - require.Equal(t, length, ret) -} - -func TestLoadAndStoreSlotDuration(t *testing.T) { - db := NewInMemoryDB(t) - base := NewBaseState(db) - - d := uint64(3000) - err := base.storeSlotDuration(d) - require.NoError(t, err) - - ret, err := base.loadSlotDuration() - require.NoError(t, err) - require.Equal(t, d, ret) -} diff --git a/dot/state/block_finalisation.go b/dot/state/block_finalisation.go index dce3645678..882117a65a 100644 --- a/dot/state/block_finalisation.go +++ b/dot/state/block_finalisation.go @@ -166,14 +166,6 @@ func (bs *BlockState) SetFinalisedHash(hash common.Hash, round, setID uint64) er logger.Tracef("pruned block number %d with hash %s", blockHeader.Number, hash) } - // if nothing was previously finalised, set the first slot of the network to the - // slot number of block 1, which is now being set as final - if bs.lastFinalised == bs.genesisHash && hash != bs.genesisHash { - if err := bs.setFirstSlotOnFinalisation(); err != nil { - return fmt.Errorf("failed to set first slot on finalisation: %w", err) - } - } - header, err := bs.GetHeader(hash) if err != nil { return fmt.Errorf("failed to get finalised header, hash: %s, error: %s", hash, err) @@ -280,17 +272,3 @@ func (bs *BlockState) handleFinalisedBlock(currentFinalizedHash common.Hash) err return batch.Flush() } - -func (bs *BlockState) setFirstSlotOnFinalisation() error { - header, err := bs.GetHeaderByNumber(1) - if err != nil { - return err - } - - slot, err := types.GetSlotFromHeader(header) - if err != nil { - return err - } - - return bs.baseState.storeFirstSlot(slot) -} diff --git a/dot/state/block_finalisation_test.go b/dot/state/block_finalisation_test.go index 17d36e8f78..f08b3d718c 100644 --- a/dot/state/block_finalisation_test.go +++ b/dot/state/block_finalisation_test.go @@ -107,7 +107,7 @@ func TestBlockState_SetFinalisedHash(t *testing.T) { require.Equal(t, testhash, h) } -func TestSetFinalisedHash_setFirstSlotOnFinalisation(t *testing.T) { +func TestSetFinalisedHash_retrieveBlockNumber1SlotNumber(t *testing.T) { bs := newTestBlockState(t, newTriesEmpty()) firstSlot := uint64(42069) @@ -152,7 +152,15 @@ func TestSetFinalisedHash_setFirstSlotOnFinalisation(t *testing.T) { require.NoError(t, err) require.Equal(t, header2.Hash(), bs.lastFinalised) - res, err := bs.baseState.loadFirstSlot() + hashes, err := bs.GetHashesByNumber(1) require.NoError(t, err) - require.Equal(t, firstSlot, res) + require.Len(t, hashes, 1) + + blockNumber1Header, err := bs.GetHeader(hashes[0]) + require.NoError(t, err) + + veryFirstSlot, err := blockNumber1Header.SlotNumber() + require.NoError(t, err) + + require.Equal(t, firstSlot, veryFirstSlot) } diff --git a/dot/state/epoch.go b/dot/state/epoch.go index 70fc31316b..11699bfab1 100644 --- a/dot/state/epoch.go +++ b/dot/state/epoch.go @@ -17,19 +17,18 @@ import ( ) var ( - ErrConfigNotFound = errors.New("config data not found") - ErrEpochNotInMemory = errors.New("epoch not found in memory map") - errHashNotInMemory = errors.New("hash not found in memory map") - errEpochNotInDatabase = errors.New("epoch data not found in the database") - errHashNotPersisted = errors.New("hash with next epoch not found in database") + ErrConfigNotFound = errors.New("config data not found") + ErrEpochNotInMemory = errors.New("epoch not found in memory map") + errEpochLengthCannotBeZero = errors.New("epoch length cannot be zero") + errHashNotInMemory = errors.New("hash not found in memory map") + errEpochNotInDatabase = errors.New("epoch data not found in the database") + errHashNotPersisted = errors.New("hash with next epoch not found in database") + errNoFirstNonOriginBlock = errors.New("no first non origin block") ) var ( epochPrefix = "epoch" - epochLengthKey = []byte("epochlength") currentEpochKey = []byte("current") - firstSlotKey = []byte("firstslot") - slotDurationKey = []byte("slotduration") epochDataPrefix = []byte("epochinfo") configDataPrefix = []byte("configinfo") latestConfigDataKey = []byte("lcfginfo") @@ -48,13 +47,21 @@ func configDataKey(epoch uint64) []byte { return append(configDataPrefix, buf...) } +// GenesisEpochDescriptor is the informations provided by calling +// the genesis WASM runtime exported function `BabeAPIConfiguration` +type GenesisEpochDescriptor struct { + EpochData *types.EpochDataRaw + ConfigData *types.ConfigData +} + // EpochState tracks information related to each epoch type EpochState struct { - db GetPutter - baseState *BaseState - blockState *BlockState - epochLength uint64 // measured in slots - skipToEpoch uint64 + db GetPutter + baseState *BaseState + blockState *BlockState + epochLength uint64 // measured in slots + slotDuration uint64 + skipToEpoch uint64 nextEpochDataLock sync.RWMutex // nextEpochData follows the format map[epoch]map[block hash]next epoch data @@ -63,64 +70,49 @@ type EpochState struct { nextConfigDataLock sync.RWMutex // nextConfigData follows the format map[epoch]map[block hash]next config data nextConfigData nextEpochMap[types.NextConfigDataV1] + + genesisEpochDescriptor *GenesisEpochDescriptor } // NewEpochStateFromGenesis returns a new EpochState given information for the first epoch, fetched from the runtime func NewEpochStateFromGenesis(db database.Database, blockState *BlockState, genesisConfig *types.BabeConfiguration) (*EpochState, error) { - baseState := NewBaseState(db) - - err := baseState.storeFirstSlot(1) // this may change once the first block is imported - if err != nil { - return nil, err - } - - epochDB := database.NewTable(db, epochPrefix) - err = epochDB.Put(currentEpochKey, []byte{0, 0, 0, 0, 0, 0, 0, 0}) - if err != nil { - return nil, err - } - if genesisConfig.EpochLength == 0 { - return nil, errors.New("epoch length is 0") + return nil, errEpochLengthCannotBeZero } s := &EpochState{ baseState: NewBaseState(db), blockState: blockState, - db: epochDB, + db: database.NewTable(db, epochPrefix), epochLength: genesisConfig.EpochLength, + slotDuration: genesisConfig.SlotDuration, nextEpochData: make(nextEpochMap[types.NextEpochData]), nextConfigData: make(nextEpochMap[types.NextConfigDataV1]), - } - epochDataRaw := &types.EpochDataRaw{ - Authorities: genesisConfig.GenesisAuthorities, - Randomness: genesisConfig.Randomness, + genesisEpochDescriptor: &GenesisEpochDescriptor{ + EpochData: &types.EpochDataRaw{ + Authorities: genesisConfig.GenesisAuthorities, + Randomness: genesisConfig.Randomness, + }, + ConfigData: &types.ConfigData{ + C1: genesisConfig.C1, + C2: genesisConfig.C2, + SecondarySlots: genesisConfig.SecondarySlots, + }, + }, } - err = s.SetEpochDataRaw(0, epochDataRaw) + err := s.StoreCurrentEpoch(0) if err != nil { - return nil, err + return nil, fmt.Errorf("storing current epoch") } - err = s.SetConfigData(0, &types.ConfigData{ - C1: genesisConfig.C1, - C2: genesisConfig.C2, - SecondarySlots: genesisConfig.SecondarySlots, - }) + err = s.setLatestConfigData(0) if err != nil { return nil, err } - if err = s.baseState.storeEpochLength(genesisConfig.EpochLength); err != nil { - return nil, err - } - - if err = s.baseState.storeSlotDuration(genesisConfig.SlotDuration); err != nil { - return nil, err - } - if err := s.baseState.storeSkipToEpoch(0); err != nil { return nil, err } @@ -129,14 +121,13 @@ func NewEpochStateFromGenesis(db database.Database, blockState *BlockState, } // NewEpochState returns a new EpochState -func NewEpochState(db database.Database, blockState *BlockState) (*EpochState, error) { - baseState := NewBaseState(db) - - epochLength, err := baseState.loadEpochLength() - if err != nil { - return nil, err +func NewEpochState(db database.Database, blockState *BlockState, + genesisConfig *types.BabeConfiguration) (*EpochState, error) { + if genesisConfig.EpochLength == 0 { + return nil, errEpochLengthCannotBeZero } + baseState := NewBaseState(db) skipToEpoch, err := baseState.loadSkipToEpoch() if err != nil { return nil, err @@ -146,30 +137,37 @@ func NewEpochState(db database.Database, blockState *BlockState) (*EpochState, e baseState: baseState, blockState: blockState, db: database.NewTable(db, epochPrefix), - epochLength: epochLength, + epochLength: genesisConfig.EpochLength, + slotDuration: genesisConfig.SlotDuration, skipToEpoch: skipToEpoch, nextEpochData: make(nextEpochMap[types.NextEpochData]), nextConfigData: make(nextEpochMap[types.NextConfigDataV1]), + genesisEpochDescriptor: &GenesisEpochDescriptor{ + EpochData: &types.EpochDataRaw{ + Authorities: genesisConfig.GenesisAuthorities, + Randomness: genesisConfig.Randomness, + }, + ConfigData: &types.ConfigData{ + C1: genesisConfig.C1, + C2: genesisConfig.C2, + SecondarySlots: genesisConfig.SecondarySlots, + }, + }, }, nil } // GetEpochLength returns the length of an epoch in slots -func (s *EpochState) GetEpochLength() (uint64, error) { - return s.baseState.loadEpochLength() +func (s *EpochState) GetEpochLength() uint64 { + return s.epochLength } // GetSlotDuration returns the duration of a slot func (s *EpochState) GetSlotDuration() (time.Duration, error) { - d, err := s.baseState.loadSlotDuration() - if err != nil { - return 0, err - } - - return time.ParseDuration(fmt.Sprintf("%dms", d)) + return time.ParseDuration(fmt.Sprintf("%dms", s.slotDuration)) } -// SetCurrentEpoch sets the current epoch -func (s *EpochState) SetCurrentEpoch(epoch uint64) error { +// StoreCurrentEpoch sets the current epoch +func (s *EpochState) StoreCurrentEpoch(epoch uint64) error { buf := make([]byte, 8) binary.LittleEndian.PutUint64(buf, epoch) return s.db.Put(currentEpochKey, buf) @@ -191,9 +189,16 @@ func (s *EpochState) GetEpochForBlock(header *types.Header) (uint64, error) { return 0, errors.New("header is nil") } - firstSlot, err := s.baseState.loadFirstSlot() + // actually the epoch number for block number #1 is epoch 0, + // epochs start from 0 and are incremented (almost, given that epochs might be skipped) + // sequentially 0...1...2, so the block number #1 belongs to epoch 0 + if header.Number == 1 { + return 0, nil + } + + chainFirstSlotNumber, err := s.retrieveFirstNonOriginBlockSlot(header.Hash()) if err != nil { - return 0, err + return 0, fmt.Errorf("retrieving very first slot number: %w", err) } slotNumber, err := header.SlotNumber() @@ -201,7 +206,7 @@ func (s *EpochState) GetEpochForBlock(header *types.Header) (uint64, error) { return 0, fmt.Errorf("getting slot number: %w", err) } - return (slotNumber - firstSlot) / s.epochLength, nil + return (slotNumber - chainFirstSlotNumber) / s.epochLength, nil } // SetEpochDataRaw sets the epoch data raw for a given epoch @@ -218,6 +223,10 @@ func (s *EpochState) SetEpochDataRaw(epoch uint64, raw *types.EpochDataRaw) erro // 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) GetEpochDataRaw(epoch uint64, header *types.Header) (*types.EpochDataRaw, error) { + if epoch == 0 { + return s.genesisEpochDescriptor.EpochData, nil + } + epochDataRaw, err := s.getEpochDataRawFromDatabase(epoch) if err != nil && !errors.Is(err, database.ErrNotFound) { return nil, fmt.Errorf("failed to retrieve epoch data from database: %w", err) @@ -268,8 +277,8 @@ func (s *EpochState) GetLatestEpochDataRaw() (*types.EpochDataRaw, error) { return s.GetEpochDataRaw(curr, nil) } -// SetConfigData sets the BABE config data for a given epoch -func (s *EpochState) SetConfigData(epoch uint64, info *types.ConfigData) error { +// StoreConfigData sets the BABE config data for a given epoch +func (s *EpochState) StoreConfigData(epoch uint64, info *types.ConfigData) error { enc, err := scale.Marshal(*info) if err != nil { return err @@ -296,6 +305,10 @@ func (s *EpochState) setLatestConfigData(epoch uint64) error { // If the header params is nil then it will search only in the database. func (s *EpochState) GetConfigData(epoch uint64, header *types.Header) (configData *types.ConfigData, err error) { for tryEpoch := int(epoch); tryEpoch >= 0; tryEpoch-- { + if tryEpoch == 0 { + return s.genesisEpochDescriptor.ConfigData, nil + } + configData, err = s.getConfigDataFromDatabase(uint64(tryEpoch)) if err != nil && !errors.Is(err, database.ErrNotFound) { return nil, fmt.Errorf("failed to retrieve config epoch from database: %w", err) @@ -439,51 +452,80 @@ func (s *EpochState) GetLatestConfigData() (*types.ConfigData, error) { return s.GetConfigData(epoch, nil) } -// GetStartSlotForEpoch returns the first slot in the given epoch. -// If 0 is passed as the epoch, it returns the start slot for the current epoch. -func (s *EpochState) GetStartSlotForEpoch(epoch uint64) (uint64, error) { - firstSlot, err := s.baseState.loadFirstSlot() +// GetStartSlotForEpoch returns the first slot in the given epoch, this method receives +// the best block hash in order to discover the correct block +func (s *EpochState) GetStartSlotForEpoch(epoch uint64, bestBlockHash common.Hash) (uint64, error) { + chainFirstSlotNumber, err := s.retrieveFirstNonOriginBlockSlot(bestBlockHash) if err != nil { - return 0, err - } + if errors.Is(err, errNoFirstNonOriginBlock) { + if epoch == 0 { + slotDuration, err := s.GetSlotDuration() + if err != nil { + return 0, fmt.Errorf("getting slot duration: %w", err) + } + return uint64(time.Now().UnixNano()) / uint64(slotDuration.Nanoseconds()), nil + } - return s.epochLength*epoch + firstSlot, nil + return 0, fmt.Errorf( + "%w: first non origin block is needed for epoch %d", + errNoFirstNonOriginBlock, + epoch) + } + return 0, fmt.Errorf("retrieving first non origin block slot: %w", err) + } + return s.epochLength*epoch + chainFirstSlotNumber, nil } -// GetEpochFromTime returns the epoch for a given time -func (s *EpochState) GetEpochFromTime(t time.Time) (uint64, error) { - slotDuration, err := s.GetSlotDuration() +// retrieveFirstNonOriginBlockSlot returns the slot number of the very first non origin block +// if there is more than one first non origin block then it uses the block hash to check ancestry +// e.g to return the correct slot number for a specific fork +func (s *EpochState) retrieveFirstNonOriginBlockSlot(blockHash common.Hash) (uint64, error) { + firstNonOriginHashes, err := s.blockState.GetHashesByNumber(1) if err != nil { - return 0, err + return 0, fmt.Errorf("getting hashes using number 1: %w", err) } - firstSlot, err := s.baseState.loadFirstSlot() - if err != nil { - return 0, err + if len(firstNonOriginHashes) == 0 { + return 0, errNoFirstNonOriginBlock } - slot := uint64(t.UnixNano()) / uint64(slotDuration.Nanoseconds()) + var firstNonOriginBlockHash common.Hash + if len(firstNonOriginHashes) == 1 { + firstNonOriginBlockHash = firstNonOriginHashes[0] + } else { + blockHeader, err := s.blockState.GetHeader(blockHash) + if err != nil { + return 0, fmt.Errorf("getting block by header: %w", err) + } - if slot < firstSlot { - return 0, errors.New("given time is before network start") - } + if blockHeader.Number == 1 { + return blockHeader.SlotNumber() + } - return (slot - firstSlot) / s.epochLength, nil -} + for _, hash := range firstNonOriginHashes { + isDescendant, err := s.blockState.IsDescendantOf(hash, blockHash) + if err != nil { + return 0, fmt.Errorf("while checking ancestry: %w", err) + } -// SetFirstSlot sets the first slot number of the network -func (s *EpochState) SetFirstSlot(slot uint64) error { - // check if block 1 was finalised already; if it has, don't set first slot again - header, err := s.blockState.GetHighestFinalisedHeader() + if isDescendant { + firstNonOriginBlockHash = hash + break + } + } + } + + firstNonGenesisHeader, err := s.blockState.GetHeader(firstNonOriginBlockHash) if err != nil { - return err + return 0, fmt.Errorf("getting first non genesis block by hash: %w", err) } - if header.Number >= 1 { - return errors.New("first slot has already been set") + chainFirstSlotNumber, err := firstNonGenesisHeader.SlotNumber() + if err != nil { + return 0, fmt.Errorf("getting slot number: %w", err) } - return s.baseState.storeFirstSlot(slot) + return chainFirstSlotNumber, nil } // SkipVerify returns whether verification for the given header should be skipped or not. @@ -627,7 +669,7 @@ func (s *EpochState) FinalizeBABENextConfigData(finalizedHeader *types.Header) e } cd := finalizedNextConfigData.ToConfigData() - err = s.SetConfigData(nextEpoch, cd) + err = s.StoreConfigData(nextEpoch, cd) if err != nil { return fmt.Errorf("cannot set config data: %w", err) } diff --git a/dot/state/epoch_test.go b/dot/state/epoch_test.go index 3dde7de29b..fb10a435de 100644 --- a/dot/state/epoch_test.go +++ b/dot/state/epoch_test.go @@ -4,7 +4,6 @@ package state import ( - "fmt" "testing" "time" @@ -13,24 +12,15 @@ import ( "github.com/ChainSafe/gossamer/lib/crypto/sr25519" "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/pkg/scale" + "github.com/ChainSafe/gossamer/tests/utils/config" "github.com/stretchr/testify/require" ) -var genesisBABEConfig = &types.BabeConfiguration{ - SlotDuration: 1000, - EpochLength: 200, - C1: 1, - C2: 4, - GenesisAuthorities: []types.AuthorityRaw{}, - Randomness: [32]byte{}, - SecondarySlots: 0, -} - func newEpochStateFromGenesis(t *testing.T) *EpochState { db := NewInMemoryDB(t) blockState := newTestBlockState(t, newTriesEmpty()) - s, err := NewEpochStateFromGenesis(db, blockState, genesisBABEConfig) + s, err := NewEpochStateFromGenesis(db, blockState, config.BABEConfigurationTestDefault) require.NoError(t, err) return s } @@ -45,7 +35,7 @@ func TestEpochState_CurrentEpoch(t *testing.T) { require.NoError(t, err) require.Equal(t, uint64(0), epoch) - err = s.SetCurrentEpoch(1) + err = s.StoreCurrentEpoch(1) require.NoError(t, err) epoch, err = s.GetCurrentEpoch() require.NoError(t, err) @@ -82,31 +72,41 @@ func TestEpochState_EpochData(t *testing.T) { func TestEpochState_GetStartSlotForEpoch(t *testing.T) { s := newEpochStateFromGenesis(t) - info := &types.EpochDataRaw{ - Randomness: [32]byte{77}, - } + // let's say first slot is 1 second after January 1, 1970 UTC + startAtTime := time.Unix(1, 0) + slotDuration := time.Millisecond * time.Duration(config.BABEConfigurationTestDefault.SlotDuration) + firstSlot := uint64(startAtTime.UnixNano()) / uint64(slotDuration.Nanoseconds()) - err := s.SetEpochDataRaw(2, info) + digest := types.NewDigest() + di, err := types.NewBabeSecondaryPlainPreDigest(0, firstSlot).ToPreRuntimeDigest() + require.NoError(t, err) + require.NotNil(t, di) + err = digest.Add(*di) require.NoError(t, err) - info = &types.EpochDataRaw{ - Randomness: [32]byte{77}, + header1 := types.Header{ + Number: 1, + Digest: digest, + ParentHash: s.blockState.genesisHash, } - err = s.SetEpochDataRaw(3, info) + err = s.blockState.AddBlock(&types.Block{ + Header: header1, + Body: types.Body{}, + }) require.NoError(t, err) - start, err := s.GetStartSlotForEpoch(0) + start, err := s.GetStartSlotForEpoch(0, header1.Hash()) require.NoError(t, err) require.Equal(t, uint64(1), start) - start, err = s.GetStartSlotForEpoch(1) + start, err = s.GetStartSlotForEpoch(1, header1.Hash()) require.NoError(t, err) - require.Equal(t, uint64(1)+s.epochLength, start) + require.Equal(t, uint64(201), start) - start, err = s.GetStartSlotForEpoch(2) + start, err = s.GetStartSlotForEpoch(2, header1.Hash()) require.NoError(t, err) - require.Equal(t, genesisBABEConfig.EpochLength*2+1, start) + require.Equal(t, uint64(401), start) } func TestEpochState_ConfigData(t *testing.T) { @@ -118,7 +118,7 @@ func TestEpochState_ConfigData(t *testing.T) { SecondarySlots: 1, } - err := s.SetConfigData(1, data) + err := s.StoreConfigData(1, data) require.NoError(t, err) ret, err := s.GetConfigData(1, nil) @@ -130,11 +130,39 @@ func TestEpochState_ConfigData(t *testing.T) { require.Equal(t, data, ret) } +func createAndImportBlockOne(t *testing.T, slotNumber uint64, blockState *BlockState) (blockOneHeader *types.Header) { + babeHeader := types.NewBabeDigest() + err := babeHeader.SetValue(*types.NewBabePrimaryPreDigest(0, slotNumber, [32]byte{}, [64]byte{})) + require.NoError(t, err) + enc, err := scale.Marshal(babeHeader) + require.NoError(t, err) + d := types.NewBABEPreRuntimeDigest(enc) + digest := types.NewDigest() + digest.Add(*d) + + blockOneHeader = &types.Header{ + Number: 1, + Digest: digest, + ParentHash: blockState.genesisHash, + } + + err = blockState.AddBlock(&types.Block{ + Header: *blockOneHeader, + Body: *types.NewBody([]types.Extrinsic{}), + }) + require.NoError(t, err) + + return blockOneHeader +} + func TestEpochState_GetEpochForBlock(t *testing.T) { s := newEpochStateFromGenesis(t) + firstSlot := uint64(1) + blockOneHeader := createAndImportBlockOne(t, firstSlot, s.blockState) + babeHeader := types.NewBabeDigest() - err := babeHeader.SetValue(*types.NewBabePrimaryPreDigest(0, s.epochLength+2, [32]byte{}, [64]byte{})) + err := babeHeader.SetValue(*types.NewBabePrimaryPreDigest(0, s.epochLength*1+1, [32]byte{}, [64]byte{})) require.NoError(t, err) enc, err := scale.Marshal(babeHeader) require.NoError(t, err) @@ -142,16 +170,24 @@ func TestEpochState_GetEpochForBlock(t *testing.T) { digest := types.NewDigest() digest.Add(*d) - header := &types.Header{ - Digest: digest, + header2 := &types.Header{ + Number: 2, + Digest: digest, + ParentHash: blockOneHeader.Hash(), } - epoch, err := s.GetEpochForBlock(header) + err = s.blockState.AddBlock(&types.Block{ + Header: *header2, + Body: *types.NewBody([]types.Extrinsic{}), + }) + require.NoError(t, err) + + epoch, err := s.GetEpochForBlock(header2) require.NoError(t, err) require.Equal(t, uint64(1), epoch) babeHeader = types.NewBabeDigest() - err = babeHeader.SetValue(*types.NewBabePrimaryPreDigest(0, s.epochLength*2+3, [32]byte{}, [64]byte{})) + err = babeHeader.SetValue(*types.NewBabePrimaryPreDigest(0, s.epochLength*2+1, [32]byte{}, [64]byte{})) require.NoError(t, err) enc, err = scale.Marshal(babeHeader) require.NoError(t, err) @@ -159,66 +195,32 @@ func TestEpochState_GetEpochForBlock(t *testing.T) { digest2 := types.NewDigest() digest2.Add(*d) - header = &types.Header{ - Digest: digest2, + header3 := &types.Header{ + Number: 3, + Digest: digest2, + ParentHash: header2.Hash(), } - epoch, err = s.GetEpochForBlock(header) + err = s.blockState.AddBlock(&types.Block{ + Header: *header3, + Body: *types.NewBody([]types.Extrinsic{}), + }) + require.NoError(t, err) + + epoch, err = s.GetEpochForBlock(header3) require.NoError(t, err) require.Equal(t, uint64(2), epoch) } func TestEpochState_SetAndGetSlotDuration(t *testing.T) { s := newEpochStateFromGenesis(t) - expected := time.Millisecond * time.Duration(genesisBABEConfig.SlotDuration) + expected := time.Millisecond * time.Duration(config.BABEConfigurationTestDefault.SlotDuration) ret, err := s.GetSlotDuration() require.NoError(t, err) require.Equal(t, expected, ret) } -func TestEpochState_GetEpochFromTime(t *testing.T) { - s := newEpochStateFromGenesis(t) - s.blockState = newTestBlockState(t, newTriesEmpty()) - - epochDuration, err := time.ParseDuration( - fmt.Sprintf("%dms", - genesisBABEConfig.SlotDuration*genesisBABEConfig.EpochLength)) - require.NoError(t, err) - - slotDuration := time.Millisecond * time.Duration(genesisBABEConfig.SlotDuration) - - start := time.Unix(1, 0) // let's say first slot is 1 second after January 1, 1970 UTC - slot := uint64(start.UnixNano()) / uint64(slotDuration.Nanoseconds()) - - err = s.SetFirstSlot(slot) - require.NoError(t, err) - - epoch, err := s.GetEpochFromTime(start) - require.NoError(t, err) - require.Equal(t, uint64(0), epoch) - - epoch, err = s.GetEpochFromTime(start.Add(epochDuration)) - require.NoError(t, err) - require.Equal(t, uint64(1), epoch) - - epoch, err = s.GetEpochFromTime(start.Add(epochDuration / 2)) - require.NoError(t, err) - require.Equal(t, uint64(0), epoch) - - epoch, err = s.GetEpochFromTime(start.Add(epochDuration * 3 / 2)) - require.NoError(t, err) - require.Equal(t, uint64(1), epoch) - - epoch, err = s.GetEpochFromTime(start.Add(epochDuration*100 + 1)) - require.NoError(t, err) - require.Equal(t, uint64(100), epoch) - - epoch, err = s.GetEpochFromTime(start.Add(epochDuration*100 - 1)) - require.NoError(t, err) - require.Equal(t, uint64(99), epoch) -} - type inMemoryBABEData[T any] struct { epoch uint64 hashes []common.Hash @@ -246,7 +248,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { } babePrimaryPreDigest := types.BabePrimaryPreDigest{ - SlotNumber: 301, // block on epoch 1 with digest for epoch 2 + SlotNumber: 301, // block on epoch 0 with digest for epoch 1 VRFOutput: [32]byte{}, VRFProof: [64]byte{}, } @@ -275,8 +277,8 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { shouldRemainInMemory int }{ "store_and_finalize_successfully": { - shouldRemainInMemory: 1, - finalizeEpoch: 2, + shouldRemainInMemory: 2, + finalizeEpoch: 1, finalizedHeader: finalizedHeader, inMemoryEpoch: []inMemoryBABEData[types.NextEpochData]{ { @@ -284,7 +286,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { hashes: []common.Hash{ common.MustHexToHash("0x9da3ce2785da743bfbc13449db7dcb7a69c07ca914276d839abe7bedc6ac8fed"), common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"), - common.MustHexToHash("0xc0096358534ec8d21d01d34b836eed476a1c343f8724fa2153dc0725ad797a90"), + finalizedHeaderHash, }, nextData: []types.NextEpochData{ { @@ -306,7 +308,6 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { hashes: []common.Hash{ common.MustHexToHash("0x5b940c7fc0a1c5a58e4d80c5091dd003303b8f18e90a989f010c1be6f392bed1"), common.MustHexToHash("0xd380bee22de487a707cbda65dd9d4e2188f736908c42cf390c8919d4f7fc547c"), - finalizedHeaderHash, }, nextData: []types.NextEpochData{ { @@ -339,13 +340,13 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { }, "cannot_finalize_hash_not_stored": { shouldRemainInMemory: 1, - finalizeEpoch: 2, + finalizeEpoch: 1, // this header hash is not in the database finalizedHeader: finalizedHeader, expectErr: errHashNotPersisted, inMemoryEpoch: []inMemoryBABEData[types.NextEpochData]{ { - epoch: 2, + epoch: 1, hashes: []common.Hash{ common.MustHexToHash("0x9da3ce2785da743bfbc13449db7dcb7a69c07ca914276d839abe7bedc6ac8fed"), common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"), @@ -388,10 +389,9 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { } require.Len(t, epochState.nextEpochData, len(tt.inMemoryEpoch)) - expectedNextEpochData := epochState.nextEpochData[tt.finalizeEpoch][tt.finalizedHeader.Hash()] - err := epochState.blockState.db.Put(headerKey(tt.finalizedHeader.Hash()), []byte{}) + err := epochState.blockState.SetHeader(tt.finalizedHeader) require.NoError(t, err) err = epochState.FinalizeBABENextEpochData(tt.finalizedHeader) @@ -413,9 +413,9 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { } } -func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { +func newBlockWithPrimaryDigest(t *testing.T, slotNumber uint64, blockNumber uint) *types.Header { babePrimaryPreDigest := types.BabePrimaryPreDigest{ - SlotNumber: 301, // block on epoch 1 with changes to epoch 2 + SlotNumber: slotNumber, // block on epoch 0 with changes to epoch 1 VRFOutput: [32]byte{}, VRFProof: [64]byte{}, } @@ -427,14 +427,21 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { require.NoError(t, digest.Add(*preRuntimeDigest)) - // finalized header for testing purposes - finalizedHeader := &types.Header{ + return &types.Header{ ParentHash: common.Hash{}, - Number: 1, + Number: blockNumber, Digest: digest, } +} - finalizedHeaderHash := finalizedHeader.Hash() +func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { + chainFirstSlotNumber := uint64(1) + blockNumber1 := newBlockWithPrimaryDigest(t, + chainFirstSlotNumber, 1) + blockNumber2 := newBlockWithPrimaryDigest(t, + chainFirstSlotNumber+config.BABEConfigurationTestDefault.EpochLength, 2) + + finalizedHeaders := []*types.Header{blockNumber1, blockNumber2} tests := map[string]struct { finalizedHeader *types.Header @@ -446,7 +453,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { "store_and_finalize_successfully": { shouldRemainInMemory: 1, finalizedEpoch: 2, - finalizedHeader: finalizedHeader, + finalizedHeader: blockNumber2, inMemoryEpoch: []inMemoryBABEData[types.NextConfigDataV1]{ { epoch: 1, @@ -478,7 +485,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { hashes: []common.Hash{ common.MustHexToHash("0x5b940c7fc0a1c5a58e4d80c5091dd003303b8f18e90a989f010c1be6f392bed1"), common.MustHexToHash("0xd380bee22de487a707cbda65dd9d4e2188f736908c42cf390c8919d4f7fc547c"), - finalizedHeaderHash, + blockNumber2.Hash(), }, nextData: []types.NextConfigDataV1{ { @@ -516,7 +523,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { "cannot_finalize_hash_doesnt_exists": { shouldRemainInMemory: 1, finalizedEpoch: 2, - finalizedHeader: finalizedHeader, // finalize when the hash does not exist + finalizedHeader: blockNumber2, // finalize when the hash does not exist expectErr: errHashNotPersisted, inMemoryEpoch: []inMemoryBABEData[types.NextConfigDataV1]{ { @@ -549,7 +556,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { "in_memory_config_not_found_shouldnt_return_error": { shouldRemainInMemory: 0, finalizedEpoch: 1, // try to finalize an epoch that does not exist - finalizedHeader: finalizedHeader, + finalizedHeader: blockNumber1, inMemoryEpoch: []inMemoryBABEData[types.NextConfigDataV1]{}, }, } @@ -558,6 +565,20 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { t.Run(testName, func(t *testing.T) { epochState := newEpochStateFromGenesis(t) + for _, finalized := range finalizedHeaders { + // mapping number #1 to the block hash + // then we can retrieve the slot number + // using the block number + err := epochState.blockState.db.Put( + headerHashKey(uint64(finalized.Number)), + finalized.Hash().ToBytes(), + ) + require.NoError(t, err) + + err = epochState.blockState.SetHeader(finalized) + require.NoError(t, err) + } + for _, e := range tt.inMemoryEpoch { for i, hash := range e.hashes { epochState.storeBABENextConfigData(e.epoch, hash, e.nextData[i]) @@ -569,17 +590,14 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { // if there is no data in memory we try to finalize the next config data // it should return nil since next epoch config data will not be in every epoch's first block if len(tt.inMemoryEpoch) == 0 { - err = epochState.FinalizeBABENextConfigData(tt.finalizedHeader) + err := epochState.FinalizeBABENextConfigData(tt.finalizedHeader) require.NoError(t, err) return } expectedConfigData := epochState.nextConfigData[tt.finalizedEpoch][tt.finalizedHeader.Hash()] - err := epochState.blockState.db.Put(headerKey(tt.finalizedHeader.Hash()), []byte{}) - require.NoError(t, err) - - err = epochState.FinalizeBABENextConfigData(tt.finalizedHeader) + err := epochState.FinalizeBABENextConfigData(tt.finalizedHeader) if tt.expectErr != nil { require.ErrorIs(t, err, tt.expectErr) } else { @@ -595,3 +613,124 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { }) } } + +func currentSlot(ts, slotDuration uint64) uint64 { + return ts / slotDuration +} + +func buildBlockPrimaryDigest(t *testing.T, primaryPreDigest types.BabePrimaryPreDigest) types.Digest { + babeDigest := types.NewBabeDigest() + err := babeDigest.SetValue(primaryPreDigest) + require.NoError(t, err) + + bdEnc, err := scale.Marshal(babeDigest) + require.NoError(t, err) + + digestPrimary := types.NewDigest() + err = digestPrimary.Add(types.PreRuntimeDigest{ + ConsensusEngineID: types.BabeEngineID, + Data: bdEnc, + }) + require.NoError(t, err) + + return digestPrimary +} + +func TestRetrieveChainFirstSlot(t *testing.T) { + // test case: setup a chain that will have two blocks with number 1 + // one created in slot X and the other created on slot Y + // slot Y is 1000 slots ahead of slot X, Gossamer should handle + // each chain correctly, blocks built on Y should have the correct + // epoch calculation, same for blocks on X + // when finalisation happens Gossamer should retrieve the chain first + // slot for the finalized chain, given that the other chain will be pruned + singleEpochState := newEpochStateFromGenesis(t) + + // calling without any block it must return error + _, err := singleEpochState.retrieveFirstNonOriginBlockSlot(common.Hash{}) + require.ErrorIs(t, err, errNoFirstNonOriginBlock) + + slotDuration, err := singleEpochState.GetSlotDuration() + require.NoError(t, err) + + genesisHash := singleEpochState.blockState.genesisHash + + slotX := currentSlot(uint64(time.Now().UnixNano()), + uint64(slotDuration.Nanoseconds())) + + block01OnSlotX := types.NewEmptyHeader() + block01OnSlotX.ParentHash = genesisHash + block01OnSlotX.Number = 1 + block01OnSlotX.Digest = buildBlockPrimaryDigest(t, + types.BabePrimaryPreDigest{AuthorityIndex: 0, SlotNumber: slotX}) + + err = singleEpochState.blockState.AddBlock( + &types.Block{Header: *block01OnSlotX, Body: *types.NewBody([]types.Extrinsic{})}) + require.NoError(t, err) + + slotY := slotX + 1000 + + block01OnSlotY := types.NewEmptyHeader() + block01OnSlotY.ParentHash = genesisHash + block01OnSlotY.Number = 1 + block01OnSlotY.Digest = buildBlockPrimaryDigest(t, + types.BabePrimaryPreDigest{AuthorityIndex: 1, SlotNumber: slotY}) + + singleEpochState.blockState.AddBlock( + &types.Block{Header: *block01OnSlotY, Body: *types.NewBody([]types.Extrinsic{})}) + require.NoError(t, err) + + // creating another block on top of each fork + block02OnSlotX := types.NewEmptyHeader() + block02OnSlotX.ParentHash = block01OnSlotX.Hash() + block02OnSlotX.Number = 2 + block02OnSlotX.Digest = buildBlockPrimaryDigest(t, + types.BabePrimaryPreDigest{AuthorityIndex: 0, SlotNumber: slotX + 1}) + + err = singleEpochState.blockState.AddBlock( + &types.Block{Header: *block02OnSlotX, Body: *types.NewBody([]types.Extrinsic{})}) + require.NoError(t, err) + + block02OnSlotY := types.NewEmptyHeader() + block02OnSlotY.ParentHash = block01OnSlotY.Hash() + block02OnSlotY.Number = 2 + block02OnSlotY.Digest = buildBlockPrimaryDigest(t, + types.BabePrimaryPreDigest{AuthorityIndex: 0, SlotNumber: slotY + 1}) + + err = singleEpochState.blockState.AddBlock( + &types.Block{Header: *block02OnSlotY, Body: *types.NewBody([]types.Extrinsic{})}) + require.NoError(t, err) + + testcases := map[string]struct { + blockHeader *types.Header + expectedChainFirstSlot uint64 + expectedSlotNumber uint64 + expectedEpoch uint64 + }{ + "block_2_on_X_fork": { + blockHeader: block02OnSlotX, + expectedChainFirstSlot: slotX, + expectedEpoch: 0, + }, + "block_2_on_Y_fork": { + blockHeader: block02OnSlotY, + expectedChainFirstSlot: slotY, + expectedEpoch: 0, + }, + } + + for tname, tt := range testcases { + tt := tt + + t.Run(tname, func(t *testing.T) { + chainFirstSlot, err := singleEpochState.retrieveFirstNonOriginBlockSlot(tt.blockHeader.Hash()) + require.NoError(t, err) + require.Equal(t, tt.expectedChainFirstSlot, chainFirstSlot) + + epoch, err := singleEpochState.GetEpochForBlock(tt.blockHeader) + require.NoError(t, err) + require.Equal(t, tt.expectedEpoch, epoch) + }) + } + +} diff --git a/dot/state/initialize.go b/dot/state/initialize.go index b32bf323ef..89c7caafc2 100644 --- a/dot/state/initialize.go +++ b/dot/state/initialize.go @@ -50,16 +50,16 @@ func (s *Service) Initialise(gen *genesis.Genesis, header *types.Header, t trie. s.Base = NewBaseState(db) - rt, err := s.CreateGenesisRuntime(t, gen) + rt, err := s.CreateGenesisRuntime(t) if err != nil { return err } + defer rt.Stop() babeCfg, err := s.loadBabeConfigurationFromRuntime(rt) if err != nil { return err } - rt.Stop() // write initial genesis values to database if err = s.storeInitialValues(gen.GenesisData(), t); err != nil { @@ -156,7 +156,7 @@ func (s *Service) storeInitialValues(data *genesis.Data, t trie.Trie) error { } // CreateGenesisRuntime creates runtime instance form genesis -func (s *Service) CreateGenesisRuntime(t trie.Trie, gen *genesis.Genesis) (runtime.Instance, error) { +func (s *Service) CreateGenesisRuntime(t trie.Trie) (runtime.Instance, error) { // load genesis state into database genTrie := rtstorage.NewTrieState(t) diff --git a/dot/state/service.go b/dot/state/service.go index cb38a371f2..6f85c9c438 100644 --- a/dot/state/service.go +++ b/dot/state/service.go @@ -23,18 +23,19 @@ var logger = log.NewFromGlobal( // Service is the struct that holds storage, block and network states type Service struct { - dbPath string - logLvl log.Level - db database.Database - isMemDB bool // set to true if using an in-memory database; only used for testing. - Base *BaseState - Storage *InmemoryStorageState - Block *BlockState - Transaction *TransactionState - Epoch *EpochState - Grandpa *GrandpaState - Slot *SlotState - closeCh chan interface{} + dbPath string + logLvl log.Level + db database.Database + isMemDB bool // set to true if using an in-memory database; only used for testing. + Base *BaseState + Storage *InmemoryStorageState + Block *BlockState + Transaction *TransactionState + Epoch *EpochState + Grandpa *GrandpaState + Slot *SlotState + closeCh chan interface{} + genesisBABEConfig *types.BabeConfiguration PrunerCfg pruner.Config Telemetry Telemetry @@ -51,11 +52,12 @@ func (s *Service) Pause() error { // Config is the default configuration used by state service. type Config struct { - Path string - LogLevel log.Level - PrunerCfg pruner.Config - Telemetry Telemetry - Metrics metrics.IntervalConfig + Path string + LogLevel log.Level + PrunerCfg pruner.Config + Telemetry Telemetry + Metrics metrics.IntervalConfig + GenesisBABEConfig *types.BabeConfiguration } // NewService create a new instance of Service @@ -63,15 +65,16 @@ func NewService(config Config) *Service { logger.Patch(log.SetLevel(config.LogLevel)) return &Service{ - dbPath: config.Path, - logLvl: config.LogLevel, - db: nil, - isMemDB: false, - Storage: nil, - Block: nil, - closeCh: make(chan interface{}), - PrunerCfg: config.PrunerCfg, - Telemetry: config.Telemetry, + dbPath: config.Path, + logLvl: config.LogLevel, + db: nil, + isMemDB: false, + Storage: nil, + Block: nil, + closeCh: make(chan interface{}), + PrunerCfg: config.PrunerCfg, + Telemetry: config.Telemetry, + genesisBABEConfig: config.GenesisBABEConfig, } } @@ -150,8 +153,10 @@ func (s *Service) Start() (err error) { // create transaction queue s.Transaction = NewTransactionState(s.Telemetry) - // create epoch state - s.Epoch, err = NewEpochState(s.db, s.Block) + // create epoch and slot state + s.Slot = NewSlotState(s.db) + + s.Epoch, err = NewEpochState(s.db, s.Block, s.genesisBABEConfig) if err != nil { return fmt.Errorf("failed to create epoch state: %w", err) } @@ -162,7 +167,6 @@ func (s *Service) Start() (err error) { "created state service with head %s, highest number %d and genesis hash %s", s.Block.BestBlockHash(), num, s.Block.genesisHash.String()) - s.Slot = NewSlotState(s.db) return nil } @@ -200,7 +204,7 @@ func (s *Service) Rewind(toBlock uint) error { return err } - err = s.Epoch.SetCurrentEpoch(epoch) + err = s.Epoch.StoreCurrentEpoch(epoch) if err != nil { return err } @@ -262,7 +266,8 @@ func (s *Service) Stop() error { // Import imports the given state corresponding to the given header and sets the head of the chain // to it. Additionally, it uses the first slot to correctly set the epoch number of the block. -func (s *Service) Import(header *types.Header, t trie.Trie, stateTrieVersion trie.TrieLayout, firstSlot uint64) error { +func (s *Service) Import(header *types.Header, t trie.Trie, + stateTrieVersion trie.TrieLayout, firstSlot uint64) error { var err error // initialise database using data directory if !s.isMemDB { @@ -273,25 +278,23 @@ func (s *Service) Import(header *types.Header, t trie.Trie, stateTrieVersion tri } block := &BlockState{ - db: database.NewTable(s.db, blockPrefix), + bt: blocktree.NewEmptyBlockTree(), + db: database.NewTable(s.db, blockPrefix), + unfinalisedBlocks: newHashToBlockMap(), } storage := &InmemoryStorageState{ db: database.NewTable(s.db, storagePrefix), } - epoch, err := NewEpochState(s.db, block) + epoch, err := NewEpochState(s.db, block, s.genesisBABEConfig) if err != nil { return err } s.Base = NewBaseState(s.db) - if err = s.Base.storeFirstSlot(firstSlot); err != nil { - return err - } - - blockEpoch, err := epoch.GetEpochForBlock(header) + blockEpoch, err := getEpochForBlockHeader(header, epoch.epochLength, firstSlot) if err != nil { return err } @@ -303,7 +306,7 @@ func (s *Service) Import(header *types.Header, t trie.Trie, stateTrieVersion tri } logger.Debugf("skip BABE verification up to epoch %d", skipTo) - if err := epoch.SetCurrentEpoch(blockEpoch); err != nil { + if err := epoch.StoreCurrentEpoch(blockEpoch); err != nil { return err } @@ -349,3 +352,12 @@ func (s *Service) Import(header *types.Header, t trie.Trie, stateTrieVersion tri return s.db.Close() } + +func getEpochForBlockHeader(header *types.Header, epochLength, chainFirstSlotNumber uint64) (uint64, error) { + slotNumber, err := header.SlotNumber() + if err != nil { + return 0, fmt.Errorf("getting slot number: %w", err) + } + + return (slotNumber - chainFirstSlotNumber) / epochLength, nil +} diff --git a/dot/state/service_integration_test.go b/dot/state/service_integration_test.go index fe0c2f4da6..15c463aa2b 100644 --- a/dot/state/service_integration_test.go +++ b/dot/state/service_integration_test.go @@ -19,6 +19,7 @@ import ( runtime "github.com/ChainSafe/gossamer/lib/runtime/storage" "github.com/ChainSafe/gossamer/pkg/trie" inmemory_trie "github.com/ChainSafe/gossamer/pkg/trie/inmemory" + "github.com/ChainSafe/gossamer/tests/utils/config" "go.uber.org/mock/gomock" "github.com/stretchr/testify/require" @@ -31,9 +32,10 @@ func newTestService(t *testing.T) (state *Service) { telemetryMock.EXPECT().SendMessage(gomock.Any()).AnyTimes() config := Config{ - Path: t.TempDir(), - LogLevel: log.Info, - Telemetry: telemetryMock, + Path: t.TempDir(), + LogLevel: log.Info, + Telemetry: telemetryMock, + GenesisBABEConfig: config.BABEConfigurationTestDefault, } state = NewService(config) return state @@ -46,9 +48,10 @@ func newTestMemDBService(t *testing.T) *Service { testDatadirPath := t.TempDir() config := Config{ - Path: testDatadirPath, - LogLevel: log.Info, - Telemetry: telemetryMock, + Path: testDatadirPath, + LogLevel: log.Info, + Telemetry: telemetryMock, + GenesisBABEConfig: config.BABEConfigurationTestDefault, } state := NewService(config) state.UseMemDB() @@ -129,9 +132,10 @@ func TestService_BlockTree(t *testing.T) { MaxTimes(2) config := Config{ - Path: t.TempDir(), - LogLevel: log.Info, - Telemetry: telemetryMock, + Path: t.TempDir(), + LogLevel: log.Info, + Telemetry: telemetryMock, + GenesisBABEConfig: config.BABEConfigurationTestDefault, } stateA := NewService(config) @@ -184,7 +188,8 @@ func TestService_StorageTriePruning(t *testing.T) { // Mode: pruner.Full, RetainedBlocks: uint32(retainBlocks), }, - Telemetry: telemetryMock, + Telemetry: telemetryMock, + GenesisBABEConfig: config.BABEConfigurationTestDefault, } serv := NewService(config) serv.UseMemDB() @@ -231,9 +236,10 @@ func TestService_PruneStorage(t *testing.T) { telemetryMock.EXPECT().SendMessage(gomock.Any()).Times(2) config := Config{ - Path: t.TempDir(), - LogLevel: log.Info, - Telemetry: telemetryMock, + Path: t.TempDir(), + LogLevel: log.Info, + Telemetry: telemetryMock, + GenesisBABEConfig: config.BABEConfigurationTestDefault, } serv := NewService(config) serv.UseMemDB() @@ -312,9 +318,10 @@ func TestService_Rewind(t *testing.T) { telemetryMock.EXPECT().SendMessage(gomock.Any()).Times(3) config := Config{ - Path: t.TempDir(), - LogLevel: log.Info, - Telemetry: telemetryMock, + Path: t.TempDir(), + LogLevel: log.Info, + Telemetry: telemetryMock, + GenesisBABEConfig: config.BABEConfigurationTestDefault, } serv := NewService(config) serv.UseMemDB() @@ -370,9 +377,10 @@ func TestService_Import(t *testing.T) { telemetryMock.EXPECT().SendMessage(gomock.Any()) config := Config{ - Path: t.TempDir(), - LogLevel: log.Info, - Telemetry: telemetryMock, + Path: t.TempDir(), + LogLevel: log.Info, + Telemetry: telemetryMock, + GenesisBABEConfig: config.BABEConfigurationTestDefault, } serv := NewService(config) serv.UseMemDB() @@ -381,6 +389,36 @@ func TestService_Import(t *testing.T) { err := serv.Initialise(&genData, &genesisHeader, genTrie) require.NoError(t, err) + babePrimaryPreDigest := types.BabePrimaryPreDigest{ + SlotNumber: 1, // block on epoch 0 with changes to epoch 1 + VRFOutput: [32]byte{}, + VRFProof: [64]byte{}, + } + + preRuntimeDigest, err := babePrimaryPreDigest.ToPreRuntimeDigest() + require.NoError(t, err) + digest := types.NewDigest() + + require.NoError(t, digest.Add(*preRuntimeDigest)) + + blockNumber01 := &types.Header{ + Digest: digest, + ParentHash: common.Hash{}, + Number: 1, + } + + // mapping number #1 to the block hash + // then we can retrieve the slot number + // using the block number + err = serv.Block.db.Put( + headerHashKey(uint64(blockNumber01.Number)), + blockNumber01.Hash().ToBytes(), + ) + require.NoError(t, err) + + err = serv.Block.SetHeader(blockNumber01) + require.NoError(t, err) + tr := inmemory_trie.NewEmptyTrie() var testCases = []string{ "asdf", @@ -394,7 +432,7 @@ func TestService_Import(t *testing.T) { tr.Put([]byte(tc), []byte(tc)) } - digest := types.NewDigest() + digest = types.NewDigest() prd, err := types.NewBabeSecondaryPlainPreDigest(0, 177).ToPreRuntimeDigest() require.NoError(t, err) err = digest.Add(*prd) @@ -405,8 +443,7 @@ func TestService_Import(t *testing.T) { Digest: digest, } - firstSlot := uint64(100) - + firstSlot := uint64(1) err = serv.Import(header, tr, trie.V0, firstSlot) require.NoError(t, err) diff --git a/dot/state/test_helpers.go b/dot/state/test_helpers.go index a9d2c03201..73fdbddfe6 100644 --- a/dot/state/test_helpers.go +++ b/dot/state/test_helpers.go @@ -56,6 +56,23 @@ type testBranch struct { depth uint } +func AddBlockToState(t *testing.T, blockState *BlockState, + number uint, digest types.Digest, parentHash common.Hash) *types.Header { + block := &types.Block{ + Header: types.Header{ + ParentHash: parentHash, + Number: number, + StateRoot: trie.EmptyHash, + Digest: digest, + }, + Body: types.Body{}, + } + + err := blockState.AddBlock(block) + require.NoError(t, err) + return &block.Header +} + // AddBlocksToState adds `depth` number of blocks to the BlockState, optionally with random branches func AddBlocksToState(t *testing.T, blockState *BlockState, depth uint, withBranches bool) ([]*types.Header, []*types.Header) { diff --git a/dot/sync/syncer_integration_test.go b/dot/sync/syncer_integration_test.go index 92287d19e7..c10643da5e 100644 --- a/dot/sync/syncer_integration_test.go +++ b/dot/sync/syncer_integration_test.go @@ -21,6 +21,7 @@ import ( wazero_runtime "github.com/ChainSafe/gossamer/lib/runtime/wazero" "github.com/ChainSafe/gossamer/lib/utils" "github.com/ChainSafe/gossamer/pkg/trie" + "github.com/ChainSafe/gossamer/tests/utils/config" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" @@ -38,9 +39,10 @@ func newTestSyncer(t *testing.T) *Service { testDatadirPath := t.TempDir() scfg := state.Config{ - Path: testDatadirPath, - LogLevel: log.Info, - Telemetry: mockTelemetryClient, + Path: testDatadirPath, + LogLevel: log.Info, + Telemetry: mockTelemetryClient, + GenesisBABEConfig: config.BABEConfigurationTestDefault, } stateSrvc := state.NewService(scfg) stateSrvc.UseMemDB() diff --git a/lib/babe/babe.go b/lib/babe/babe.go index a7aa8d3c19..02fda92c2a 100644 --- a/lib/babe/babe.go +++ b/lib/babe/babe.go @@ -89,11 +89,6 @@ func (Builder) NewServiceIFace(cfg *ServiceConfig) (service *Service, err error) return nil, fmt.Errorf("cannot get slot duration: %w", err) } - epochLength, err := cfg.EpochState.GetEpochLength() - if err != nil { - return nil, fmt.Errorf("cannot get epoch length: %w", err) - } - ctx, cancel := context.WithCancel(context.Background()) babeService := &Service{ @@ -110,7 +105,7 @@ func (Builder) NewServiceIFace(cfg *ServiceConfig) (service *Service, err error) blockImportHandler: cfg.BlockImportHandler, constants: constants{ slotDuration: slotDuration, - epochLength: epochLength, + epochLength: cfg.EpochState.GetEpochLength(), }, telemetry: cfg.Telemetry, } @@ -136,11 +131,6 @@ func NewService(cfg *ServiceConfig) (*Service, error) { return nil, fmt.Errorf("cannot get slot duration: %w", err) } - epochLength, err := cfg.EpochState.GetEpochLength() - if err != nil { - return nil, fmt.Errorf("cannot get epoch length: %w", err) - } - ctx, cancel := context.WithCancel(context.Background()) babeService := &Service{ @@ -157,7 +147,7 @@ func NewService(cfg *ServiceConfig) (*Service, error) { blockImportHandler: cfg.BlockImportHandler, constants: constants{ slotDuration: slotDuration, - epochLength: epochLength, + epochLength: cfg.EpochState.GetEpochLength(), }, telemetry: cfg.Telemetry, } @@ -257,9 +247,9 @@ func (b *Service) Stop() error { return nil } -// Authorities returns the current BABE authorities +// AuthoritiesRaw returns the current BABE authorities func (b *Service) AuthoritiesRaw() []types.AuthorityRaw { - return b.epochHandler.epochData.authorities + return b.epochHandler.descriptor.data.authorities } // IsStopped returns true if the service is stopped (ie not producing blocks) @@ -293,22 +283,16 @@ func (b *Service) initiate() { } func (b *Service) initiateAndGetEpochHandler(epoch uint64) (*epochHandler, error) { - epochData, err := b.initiateEpoch(epoch) + epochDescriptor, err := b.initiateEpoch(epoch) if err != nil { return nil, fmt.Errorf("failed to initiate epoch: %w", err) } logger.Debugf("initiated epoch with threshold %s, randomness 0x%x and authorities %v", - epochData.threshold, epochData.randomness[:], epochData.authorities) + epochDescriptor.data.threshold, epochDescriptor.data.randomness[:], epochDescriptor.data.authorities) - epochStartSlot, err := b.epochState.GetStartSlotForEpoch(epoch) - if err != nil { - return nil, fmt.Errorf("failed to get start slot for current epoch %d: %w", epoch, err) - } - - return newEpochHandler(epoch, - epochStartSlot, - epochData, + return newEpochHandler( + epochDescriptor, b.constants, b.handleSlot, b.keypair, @@ -347,13 +331,8 @@ func (b *Service) handleEpoch(epoch uint64) (next uint64, err error) { return 0, fmt.Errorf("cannot initiate and get epoch handler: %w", err) } - // get start slot for current epoch - nextEpochStart, err := b.epochState.GetStartSlotForEpoch(epoch + 1) - if err != nil { - return 0, fmt.Errorf("failed to get start slot for next epoch %d: %w", epoch+1, err) - } - - nextEpochStartTime := getSlotStartTime(nextEpochStart, b.constants.slotDuration) + nextEpochStarts := b.epochHandler.descriptor.endSlot + nextEpochStartTime := getSlotStartTime(nextEpochStarts, b.constants.slotDuration) epochTimer := time.NewTimer(time.Until(nextEpochStartTime)) errCh := make(chan error, 1) diff --git a/lib/babe/babe_integration_test.go b/lib/babe/babe_integration_test.go index 1a2c2f303e..888b2251f2 100644 --- a/lib/babe/babe_integration_test.go +++ b/lib/babe/babe_integration_test.go @@ -6,19 +6,35 @@ package babe import ( + "fmt" "testing" "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/runtime" - "github.com/centrifuge/go-substrate-rpc-client/v4/signature" - "go.uber.org/mock/gomock" "github.com/stretchr/testify/require" ) +var AuthorOnEverySlotBABEConfig = &types.BabeConfiguration{ + // slots are 6 seconds on westend and using time.Now() allows us to create a block at any point in the slot. + // So we need to manually set time to produce consistent results. See here: + // https://github.com/paritytech/substrate/blob/09de7b41599add51cf27eca8f1bc4c50ed8e9453/frame/timestamp/src/lib.rs#L229 + // https://github.com/paritytech/substrate/blob/09de7b41599add51cf27eca8f1bc4c50ed8e9453/frame/timestamp/src/lib.rs#L206 + SlotDuration: 6000, + EpochLength: 200, + C1: 1, + C2: 1, + GenesisAuthorities: []types.AuthorityRaw{ + { + Key: keyring.Alice().Public().(*sr25519.PublicKey).AsBytes(), + Weight: 1, + }, + }, + Randomness: [32]byte{}, + SecondarySlots: 0, +} + func TestService_SlotDuration(t *testing.T) { duration, err := time.ParseDuration("1000ms") require.NoError(t, err) @@ -34,24 +50,22 @@ func TestService_SlotDuration(t *testing.T) { } func TestService_ProducesBlocks(t *testing.T) { - ctrl := gomock.NewController(t) - - blockImportHandler := NewMockBlockImportHandler(ctrl) - blockImportHandler.EXPECT().HandleBlockProduced(gomock.Any(), gomock.Any()). - Return(nil).MinTimes(2) cfg := ServiceConfig{ - Authority: true, - BlockImportHandler: blockImportHandler, + Authority: true, } gen, genTrie, genHeader := newWestendDevGenesisWithTrieAndHeader(t) - babeService := createTestService(t, cfg, gen, genTrie, genHeader, nil) + babeService := createTestService(t, cfg, gen, genTrie, genHeader, AuthorOnEverySlotBABEConfig) err := babeService.Start() require.NoError(t, err) time.Sleep(babeService.constants.slotDuration * 2) err = babeService.Stop() require.NoError(t, err) + + bestHeader, err := babeService.blockState.BestBlockHeader() + require.NoError(t, err) + require.GreaterOrEqual(t, bestHeader.Number, uint(2)) } func TestService_GetAuthorityIndex(t *testing.T) { @@ -91,7 +105,7 @@ func TestService_GetAuthorityIndex(t *testing.T) { func TestStartAndStop(t *testing.T) { cfg := ServiceConfig{} gen, genTrie, genHeader := newWestendLocalGenesisWithTrieAndHeader(t) - bs := createTestService(t, cfg, gen, genTrie, genHeader, nil) + bs := createTestService(t, cfg, gen, genTrie, genHeader, AuthorOnEverySlotBABEConfig) err := bs.Start() require.NoError(t, err) err = bs.Stop() @@ -104,7 +118,7 @@ func TestService_PauseAndResume(t *testing.T) { cfg := ServiceConfig{} genesis, genesisTrie, genesisHeader := newWestendLocalGenesisWithTrieAndHeader(t) - babeService := createTestService(t, cfg, genesis, genesisTrie, genesisHeader, nil) + babeService := createTestService(t, cfg, genesis, genesisTrie, genesisHeader, AuthorOnEverySlotBABEConfig) err := babeService.Start() require.NoError(t, err) time.Sleep(time.Second) @@ -137,35 +151,45 @@ func TestService_HandleSlotWithLaggingSlot(t *testing.T) { } genesis, genesisTrie, genesisHeader := newWestendDevGenesisWithTrieAndHeader(t) - babeService := createTestService(t, cfg, genesis, genesisTrie, genesisHeader, nil) - err := babeService.Start() - require.NoError(t, err) - defer func() { - err = babeService.Stop() - require.NoError(t, err) - }() + babeService := createTestService(t, cfg, genesis, genesisTrie, genesisHeader, AuthorOnEverySlotBABEConfig) - parentHash := babeService.blockState.GenesisHash() bestBlockHash := babeService.blockState.BestBlockHash() rt, err := babeService.blockState.GetRuntime(bestBlockHash) require.NoError(t, err) - bestBlockHeader, err := babeService.blockState.GetHeader(bestBlockHash) + epochDescriptor, err := babeService.initiateEpoch(testEpochIndex) require.NoError(t, err) - epochData, err := babeService.initiateEpoch(testEpochIndex) + startTime := getSlotStartTime(epochDescriptor.startSlot, babeService.constants.slotDuration) + + slot := Slot{ + start: startTime, + duration: babeService.constants.slotDuration, + number: epochDescriptor.startSlot, + } + + preRuntimeDigest, err := claimSlot( + testEpochIndex, slot.number, epochDescriptor.data, babeService.keypair) require.NoError(t, err) - timestamp := time.Unix(6, 0) - slot := getSlot(t, rt, timestamp) - ext := runtime.NewTestExtrinsic(t, rt, parentHash, parentHash, 0, signature.TestKeyringPairAlice, - "System.remark", []byte{0xab, 0xcd}) - block := createTestBlockWithSlot(t, babeService, bestBlockHeader, [][]byte{common.MustHexToBytes(ext)}, - testEpochIndex, epochData, slot) + const authorityIndex = 0 // alice + builder := NewBlockBuilder( + babeService.keypair, + babeService.transactionState, + babeService.blockState, + authorityIndex, + preRuntimeDigest, + ) + + block, err := builder.buildBlock(&genesisHeader, slot, rt) + require.NoError(t, err) + + fmt.Println(epochDescriptor.startSlot) err = babeService.blockState.AddBlock(block) require.NoError(t, err) + time.Sleep(babeService.constants.slotDuration) header, err := babeService.blockState.BestBlockHeader() @@ -180,12 +204,11 @@ func TestService_HandleSlotWithLaggingSlot(t *testing.T) { duration: babeService.constants.slotDuration * time.Millisecond, number: slotnum, } - preRuntimeDigest, err := types.NewBabePrimaryPreDigest( + preRuntimeDigest, err = types.NewBabePrimaryPreDigest( 0, slot.number, [sr25519.VRFOutputLength]byte{}, [sr25519.VRFProofLength]byte{}, ).ToPreRuntimeDigest() - require.NoError(t, err) slot = Slot{ @@ -194,28 +217,34 @@ func TestService_HandleSlotWithLaggingSlot(t *testing.T) { number: bestBlockSlotNum - 1, } err = babeService.handleSlot( - babeService.epochHandler.epochNumber, + epochDescriptor.epoch, slot, - babeService.epochHandler.epochData.authorityIndex, + epochDescriptor.data.authorityIndex, preRuntimeDigest) - require.ErrorIs(t, err, errLaggingSlot) } func TestService_HandleSlotWithSameSlot(t *testing.T) { genesis, genesisTrie, genesisHeader := newWestendDevGenesisWithTrieAndHeader(t) - babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, nil) + babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, AuthorOnEverySlotBABEConfig) const authorityIndex = 0 bestBlockHash := babeService.blockState.BestBlockHash() runtime, err := babeService.blockState.GetRuntime(bestBlockHash) require.NoError(t, err) - epochData, err := babeService.initiateEpoch(testEpochIndex) + epochDescriptor, err := babeService.initiateEpoch(testEpochIndex) require.NoError(t, err) - slot := getSlot(t, runtime, time.Unix(6, 0)) - preRuntimeDigest, err := claimSlot(testEpochIndex, slot.number, epochData, babeService.keypair) + startTimestamp := getSlotStartTime(epochDescriptor.startSlot, babeService.constants.slotDuration) + slot := Slot{ + start: startTimestamp, + duration: babeService.constants.slotDuration, + number: epochDescriptor.startSlot, + } + + preRuntimeDigest, err := claimSlot( + epochDescriptor.epoch, slot.number, epochDescriptor.data, babeService.keypair) require.NoError(t, err) builder := NewBlockBuilder( @@ -234,7 +263,7 @@ func TestService_HandleSlotWithSameSlot(t *testing.T) { Keypair: keyring.Bob().(*sr25519.Keypair), } genBob, genTrieBob, genHeaderBob := newWestendDevGenesisWithTrieAndHeader(t) - babeServiceBob := createTestService(t, cfgBob, genBob, genTrieBob, genHeaderBob, nil) + babeServiceBob := createTestService(t, cfgBob, genBob, genTrieBob, genHeaderBob, AuthorOnEverySlotBABEConfig) // Add block created by alice to bob err = babeServiceBob.blockState.AddBlock(block) @@ -242,14 +271,7 @@ func TestService_HandleSlotWithSameSlot(t *testing.T) { // If the slot we are claiming is the same as the slot of the best block header, test that we can // still claim the slot without error. - bestBlockSlotNum, err := babeServiceBob.blockState.GetSlotForBlock(block.Header.Hash()) - require.NoError(t, err) - slot = Slot{ - start: time.Unix(6, 0), - duration: babeServiceBob.constants.slotDuration * time.Millisecond, - number: bestBlockSlotNum, - } preRuntimeDigest, err = types.NewBabePrimaryPreDigest( 0, slot.number, [sr25519.VRFOutputLength]byte{}, diff --git a/lib/babe/build.go b/lib/babe/build.go index 42d3db33e6..a19e085d2f 100644 --- a/lib/babe/build.go +++ b/lib/babe/build.go @@ -175,7 +175,7 @@ func (b *BlockBuilder) buildBlockExtrinsics(slot Slot, rt ExtrinsicHandler) []*t var included []*transaction.ValidTransaction slotEnd := slot.start.Add(slot.duration * 2 / 3) // reserve last 1/3 of slot for block finalisation - timeout := time.Until(slotEnd) + timeout := slotEnd.Sub(slot.start) // timeout relative to the slot start slotTimer := time.NewTimer(timeout) for { diff --git a/lib/babe/build_integration_test.go b/lib/babe/build_integration_test.go index bfe2ff4a26..fc9fbbabb1 100644 --- a/lib/babe/build_integration_test.go +++ b/lib/babe/build_integration_test.go @@ -8,7 +8,6 @@ package babe import ( "bytes" "testing" - "time" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" @@ -56,21 +55,25 @@ func TestSeal(t *testing.T) { func TestBuildBlock_ok(t *testing.T) { genesis, genesisTrie, genesisHeader := newWestendDevGenesisWithTrieAndHeader(t) - babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, nil) + babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, AuthorOnEverySlotBABEConfig) parentHash := babeService.blockState.GenesisHash() bestBlockHash := babeService.blockState.BestBlockHash() rt, err := babeService.blockState.GetRuntime(bestBlockHash) require.NoError(t, err) - testEpochData, err := babeService.initiateEpoch(testEpochIndex) + epochDescriptor, err := babeService.initiateEpoch(testEpochIndex) require.NoError(t, err) - slot := getSlot(t, rt, time.Now()) + slot := Slot{ + start: getSlotStartTime(epochDescriptor.startSlot, babeService.constants.slotDuration), + duration: babeService.constants.slotDuration, + number: epochDescriptor.startSlot, + } extrinsic := runtime.NewTestExtrinsic(t, rt, parentHash, parentHash, 0, signature.TestKeyringPairAlice, "System.remark", []byte{0xab, 0xcd}) block := createTestBlockWithSlot(t, babeService, emptyHeader, [][]byte{common.MustHexToBytes(extrinsic)}, - testEpochIndex, testEpochData, slot) + epochDescriptor, slot) const expectedSecondExtrinsic = "0x042d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" //nolint:lll expectedBlockHeader := &types.Header{ @@ -95,18 +98,23 @@ func TestBuildBlock_ok(t *testing.T) { func TestApplyExtrinsicAfterFirstBlockFinalized(t *testing.T) { genesis, genesisTrie, genesisHeader := newWestendDevGenesisWithTrieAndHeader(t) - babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, nil) + babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, AuthorOnEverySlotBABEConfig) const authorityIndex = 0 bestBlockHash := babeService.blockState.BestBlockHash() rt, err := babeService.blockState.GetRuntime(bestBlockHash) require.NoError(t, err) - epochData, err := babeService.initiateEpoch(testEpochIndex) + epochDescriptor, err := babeService.initiateEpoch(testEpochIndex) require.NoError(t, err) - slot := getSlot(t, rt, time.Now()) - preRuntimeDigest, err := claimSlot(testEpochIndex, slot.number, epochData, babeService.keypair) + slot := Slot{ + start: getSlotStartTime(epochDescriptor.startSlot, babeService.constants.slotDuration), + duration: babeService.constants.slotDuration, + number: epochDescriptor.startSlot, + } + preRuntimeDigest, err := claimSlot( + testEpochIndex, slot.number, epochDescriptor.data, babeService.keypair) require.NoError(t, err) builder := NewBlockBuilder( @@ -149,8 +157,12 @@ func TestApplyExtrinsicAfterFirstBlockFinalized(t *testing.T) { require.NoError(t, err) // Add 7 seconds to allow slot to be claimed at appropriate time, Westend has 6 second slot times - slot2 := getSlot(t, rt, time.Now().Add(7*time.Second)) - preRuntimeDigest2, err := claimSlot(testEpochIndex, slot2.number, epochData, babeService.keypair) + slot2 := Slot{ + start: getSlotStartTime(epochDescriptor.startSlot+1, babeService.constants.slotDuration), + duration: babeService.constants.slotDuration, + number: epochDescriptor.startSlot + 1, + } + preRuntimeDigest2, err := claimSlot(testEpochIndex, slot2.number, epochDescriptor.data, babeService.keypair) require.NoError(t, err) digest2 := types.NewDigest() @@ -176,7 +188,7 @@ func TestBuildAndApplyExtrinsic(t *testing.T) { require.NoError(t, err) genesis, genesisTrie, genesisHeader := newWestendLocalGenesisWithTrieAndHeader(t) - babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, nil) + babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, AuthorOnEverySlotBABEConfig) header := types.NewHeader(genesisHeader.Hash(), common.Hash{}, common.Hash{}, 1, types.NewDigest()) bestBlockHash := babeService.blockState.BestBlockHash() @@ -253,7 +265,7 @@ func TestBuildAndApplyExtrinsic_InvalidPayment(t *testing.T) { require.NoError(t, err) genesis, genesisTrie, genesisHeader := newWestendDevGenesisWithTrieAndHeader(t) - babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, nil) + babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, AuthorOnEverySlotBABEConfig) header := types.NewHeader(genesisHeader.Hash(), common.Hash{}, common.Hash{}, 1, types.NewDigest()) bestBlockHash := babeService.blockState.BestBlockHash() @@ -335,22 +347,23 @@ func TestBuildBlockTimeMonitor(t *testing.T) { metrics.Unregister(buildBlockTimer) genesis, genesisTrie, genesisHeader := newWestendDevGenesisWithTrieAndHeader(t) - babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, nil) + babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, AuthorOnEverySlotBABEConfig) parent, err := babeService.blockState.BestBlockHeader() require.NoError(t, err) - runtime, err := babeService.blockState.GetRuntime(parent.Hash()) - require.NoError(t, err) - timerMetrics := metrics.GetOrRegisterTimer(buildBlockTimer, nil) timerMetrics.Stop() - epochData, err := babeService.initiateEpoch(testEpochIndex) + epochDescriptor, err := babeService.initiateEpoch(testEpochIndex) require.NoError(t, err) - slot := getSlot(t, runtime, time.Now()) - createTestBlockWithSlot(t, babeService, parent, [][]byte{}, testEpochIndex, epochData, slot) + slot := Slot{ + start: getSlotStartTime(epochDescriptor.startSlot, babeService.constants.slotDuration), + duration: babeService.constants.slotDuration, + number: epochDescriptor.startSlot, + } + createTestBlockWithSlot(t, babeService, parent, [][]byte{}, epochDescriptor, slot) require.Equal(t, int64(1), timerMetrics.Snapshot().Count()) // TODO: there isn't an easy way to trigger an error in buildBlock from here diff --git a/lib/babe/epoch.go b/lib/babe/epoch.go index 69b8493b2b..fce0597a1b 100644 --- a/lib/babe/epoch.go +++ b/lib/babe/epoch.go @@ -11,108 +11,128 @@ import ( "github.com/ChainSafe/gossamer/lib/crypto/sr25519" ) +var errEpochLowerThanExpected = errors.New("epoch lower than expected") + +type epochDescriptor struct { + data *epochData + epoch uint64 + startSlot uint64 + endSlot uint64 +} + // initiateEpoch sets the epochData for the given epoch, runs the lottery for the slots in the epoch, // and stores updated EpochInfo in the database -func (b *Service) initiateEpoch(epoch uint64) (*epochData, error) { - logger.Debugf("initiating epoch %d", epoch) - - // if epoch == 1, check that first slot is still set correctly - // ie. that the start slot of the network is the same as the slot number of block 1 - if epoch == 1 { - if err := b.checkAndSetFirstSlot(); err != nil { - return nil, fmt.Errorf("cannot check and set first slot: %w", err) - } - } +func (b *Service) initiateEpoch(epoch uint64) (*epochDescriptor, error) { + logger.Debugf("initiating epoch %d with %d slots", epoch, b.constants.epochLength) bestBlockHeader, err := b.blockState.BestBlockHeader() if err != nil { return nil, fmt.Errorf("cannot get the best block header: %w", err) } - epochData, err := b.getEpochData(epoch, bestBlockHeader) + skipped, diff, err := b.checkIfEpochSkipped(epoch, bestBlockHeader) if err != nil { - return nil, fmt.Errorf("cannot get epoch data and start slot: %w", err) + return nil, fmt.Errorf("checking if epoch skipped: %w", err) } - startSlot, err := b.epochState.GetStartSlotForEpoch(epoch) + epochToFindData := epoch + if skipped { + // subtract 1 since we consider 0 as a valid epoch, then skipping from epoch + // 0 to epoch 2 we actually skipped only one epoch (epoch 1), however when + // searching for a data we should subtract the diff + lastKnownEpoch := epoch - diff + epochsSkipped := epoch - (diff - 1) + logger.Warnf("⏩ A total of %d epochs were skipped, from %d to %d", + epochsSkipped, lastKnownEpoch, epoch) + + // we should use the epoch data already setup for the + // last known epoch + 1, e.g we produced blocks in epoch + // 5, the first block in epoch 5 gives us the next epoch data + // that should be used to initiate epoch 6, however we skipt epoch + // 6 and we're now initialising epoch 7, so we should use the epoch + // data that were meant to be used by 6 + epochToFindData = lastKnownEpoch + 1 + } + + epochData, err := b.getEpochData(epochToFindData, bestBlockHeader) if err != nil { - return nil, fmt.Errorf("cannot get start slot for epoch %d: %w", epoch, err) + return nil, fmt.Errorf("cannot get epoch data and start slot: %w", err) } - // if we're at genesis, we need to determine when the first slot of the network will be - // by checking when we will be able to produce block 1. - // note that this assumes there will only be one producer of block 1 + // if we're at genesis or epoch was skipped then we can estimate when the start + // slot of the epoch will be, the estimation is used to calculate the epoch end + // TODO: check how substrate deals with these estimation if bestBlockHeader.Hash() == b.blockState.GenesisHash() { - startSlot, err = b.getFirstAuthoringSlot(epoch, epochData) + startSlot, err := b.getFirstAuthoringSlot(epoch, epochData) if err != nil { return nil, fmt.Errorf("cannot get first authoring slot: %w", err) } - logger.Debugf("estimated first slot as %d based on building block 1", startSlot) + logger.Debugf("estimated first slot as %d for epoch %d", startSlot, epoch) + return &epochDescriptor{ + data: epochData, + epoch: epoch, + startSlot: startSlot, + endSlot: startSlot + b.constants.epochLength, + }, nil + } - // we are at genesis, set first slot by checking at which slot we will be able to produce block 1 - if err = b.epochState.SetFirstSlot(startSlot); err != nil { - return nil, fmt.Errorf("cannot set first slot: %w", err) - } + startSlot, err := b.epochState.GetStartSlotForEpoch(epoch, bestBlockHeader.Hash()) + if err != nil { + return nil, fmt.Errorf("cannot get start slot for epoch %d: %w", epoch, err) } logger.Infof("initiating epoch %d with start slot %d", epoch, startSlot) - return epochData, nil + return &epochDescriptor{ + data: epochData, + epoch: epoch, + startSlot: startSlot, + endSlot: startSlot + b.constants.epochLength, + }, nil } -func (b *Service) checkAndSetFirstSlot() error { - firstSlot, err := b.epochState.GetStartSlotForEpoch(0) - if err != nil { - return fmt.Errorf("cannot set first slot: %w", err) +func (b *Service) checkIfEpochSkipped(epochBeingInitialized uint64, bestBlock *types.Header) ( + skipped bool, diff uint64, err error) { + if epochBeingInitialized == 0 { + return false, 0, nil } - block, err := b.blockState.GetBlockByNumber(1) + epochFromBestBlock, err := b.epochState.GetEpochForBlock(bestBlock) if err != nil { - return fmt.Errorf("cannot get block with number 1: %w", err) + return false, 0, fmt.Errorf("getting epoch for block: %w", err) } - slot, err := types.GetSlotFromHeader(&block.Header) - if err != nil { - return fmt.Errorf("cannot get slot from header of block 1: %w", err) + if epochBeingInitialized < epochFromBestBlock { + return false, 0, fmt.Errorf("%w: expected %d, got: %d", + errEpochLowerThanExpected, epochBeingInitialized, epochFromBestBlock) } - if slot != firstSlot { - if err := b.epochState.SetFirstSlot(slot); err != nil { - return fmt.Errorf("cannot set first slot for block 1: %w", err) - } + if epochFromBestBlock+1 == epochBeingInitialized { + return false, 0, nil } - return nil + return epochBeingInitialized > epochFromBestBlock, epochBeingInitialized - epochFromBestBlock, nil } func (b *Service) getEpochData(epoch uint64, bestBlock *types.Header) (*epochData, error) { - if epoch == 0 { - epochData, err := b.getLatestEpochData() - if err != nil { - return nil, fmt.Errorf("cannot get latest epoch data: %w", err) - } - - return epochData, nil - } - currEpochData, err := b.epochState.GetEpochDataRaw(epoch, bestBlock) if err != nil { - return nil, fmt.Errorf("cannot get epoch data for epoch %d: %w", epoch, err) + return nil, fmt.Errorf("getting epoch data for epoch %d: %w", epoch, err) } - currentConfigData, err := b.epochState.GetConfigData(epoch, bestBlock) + currConfigData, err := b.epochState.GetConfigData(epoch, bestBlock) if err != nil { - return nil, fmt.Errorf("cannot get config data for epoch %d: %w", epoch, err) + return nil, fmt.Errorf("getting config data for epoch %d: %w", epoch, err) } - threshold, err := CalculateThreshold(currentConfigData.C1, currentConfigData.C2, len(currEpochData.Authorities)) + threshold, err := CalculateThreshold(currConfigData.C1, currConfigData.C2, len(currEpochData.Authorities)) if err != nil { - return nil, fmt.Errorf("cannot calculate threshold: %w", err) + return nil, fmt.Errorf("calculating threshold: %w", err) } idx, err := b.getAuthorityIndex(currEpochData.Authorities) if err != nil { - return nil, fmt.Errorf("cannot get authority index: %w", err) + return nil, fmt.Errorf("getting authority index: %w", err) } return &epochData{ @@ -120,45 +140,10 @@ func (b *Service) getEpochData(epoch uint64, bestBlock *types.Header) (*epochDat authorities: currEpochData.Authorities, authorityIndex: idx, threshold: threshold, - allowedSlots: types.AllowedSlots(currentConfigData.SecondarySlots), + allowedSlots: types.AllowedSlots(currConfigData.SecondarySlots), }, nil } -func (b *Service) getLatestEpochData() (resEpochData *epochData, error error) { - resEpochData = &epochData{} - - epochDataRaw, err := b.epochState.GetLatestEpochDataRaw() - if err != nil { - return nil, fmt.Errorf("cannot get latest epoch data: %w", err) - } - - resEpochData.randomness = epochDataRaw.Randomness - resEpochData.authorities = epochDataRaw.Authorities - - configData, err := b.epochState.GetLatestConfigData() - if err != nil { - return nil, fmt.Errorf("cannot get epoch state latest config data: %w", err) - } - - resEpochData.allowedSlots = types.AllowedSlots(configData.SecondarySlots) - - resEpochData.threshold, err = CalculateThreshold(configData.C1, configData.C2, len(resEpochData.authorities)) - if err != nil { - return nil, fmt.Errorf("cannot calculate threshold: %w", err) - } - - if !b.authority { - return resEpochData, nil - } - - resEpochData.authorityIndex, err = b.getAuthorityIndex(resEpochData.authorities) - if err != nil { - return nil, fmt.Errorf("cannot get authority index: %w", err) - } - - return resEpochData, nil -} - func (b *Service) getFirstAuthoringSlot(epoch uint64, epochData *epochData) (uint64, error) { startSlot := getCurrentSlot(b.constants.slotDuration) for i := startSlot; i < startSlot+b.constants.epochLength; i++ { @@ -184,7 +169,7 @@ func (b *Service) incrementEpoch() (uint64, error) { } next := epoch + 1 - err = b.epochState.SetCurrentEpoch(next) + err = b.epochState.StoreCurrentEpoch(next) if err != nil { return 0, err } diff --git a/lib/babe/epoch_handler.go b/lib/babe/epoch_handler.go index 52dc8c9132..ac42e5afc1 100644 --- a/lib/babe/epoch_handler.go +++ b/lib/babe/epoch_handler.go @@ -15,35 +15,31 @@ import ( type handleSlotFunc = func(epoch uint64, slot Slot, authorityIndex uint32, preRuntimeDigest *types.PreRuntimeDigest) error -var ( - errEpochPast = errors.New("cannot run epoch that has already passed") -) +var errEpochPast = errors.New("cannot run epoch that has already passed") type epochHandler struct { slotHandler slotHandler - epochNumber uint64 - firstSlot uint64 - - constants constants - epochData *epochData + descriptor *epochDescriptor + constants constants slotToPreRuntimeDigest map[uint64]*types.PreRuntimeDigest handleSlot handleSlotFunc } -func newEpochHandler(epochNumber, firstSlot uint64, epochData *epochData, constants constants, +func newEpochHandler(epochDescriptor *epochDescriptor, constants constants, handleSlot handleSlotFunc, keypair *sr25519.Keypair) (*epochHandler, error) { + // determine which slots we'll be authoring in by pre-calculating VRF output slotToPreRuntimeDigest := make(map[uint64]*types.PreRuntimeDigest, constants.epochLength) - for i := firstSlot; i < firstSlot+constants.epochLength; i++ { - preRuntimeDigest, err := claimSlot(epochNumber, i, epochData, keypair) + for i := epochDescriptor.startSlot; i < epochDescriptor.endSlot; i++ { + preRuntimeDigest, err := claimSlot(epochDescriptor.epoch, i, epochDescriptor.data, keypair) if err == nil { slotToPreRuntimeDigest[i] = preRuntimeDigest continue } - if errors.Is(err, errNotOurTurnToPropose) { + if errors.Is(err, errNotOurTurnToPropose) || errors.Is(err, errOverPrimarySlotThreshold) { continue } @@ -52,10 +48,8 @@ func newEpochHandler(epochNumber, firstSlot uint64, epochData *epochData, consta return &epochHandler{ slotHandler: newSlotHandler(constants.slotDuration), - epochNumber: epochNumber, - firstSlot: firstSlot, + descriptor: epochDescriptor, constants: constants, - epochData: epochData, handleSlot: handleSlot, slotToPreRuntimeDigest: slotToPreRuntimeDigest, }, nil @@ -69,15 +63,15 @@ func (h *epochHandler) run(ctx context.Context, errCh chan<- error) { // if currSlot < h.firstSlot, it means we're at genesis and waiting for the first slot to arrive. // we have to check it here to prevent int overflow. - if currSlot >= h.firstSlot && currSlot-h.firstSlot > h.constants.epochLength { + if currSlot >= h.descriptor.startSlot && currSlot-h.descriptor.startSlot > h.constants.epochLength { logger.Warnf("attempted to start epoch that has passed: current slot=%d, start slot of epoch=%d", - currSlot, h.firstSlot, + currSlot, h.descriptor.startSlot, ) errCh <- errEpochPast return } - logger.Debugf("authoring in %d slots in epoch %d", len(h.slotToPreRuntimeDigest), h.epochNumber) + logger.Debugf("authoring in %d slots in epoch %d", len(h.slotToPreRuntimeDigest), h.descriptor.epoch) for { currentSlot, err := h.slotHandler.waitForNextSlot(ctx) @@ -92,7 +86,11 @@ func (h *epochHandler) run(ctx context.Context, errCh chan<- error) { continue } - err = h.handleSlot(h.epochNumber, currentSlot, h.epochData.authorityIndex, preRuntimeDigest) + err = h.handleSlot( + h.descriptor.epoch, + currentSlot, + h.descriptor.data.authorityIndex, + preRuntimeDigest) if err != nil { logger.Warnf("failed to handle slot %d: %s", currentSlot.number, err) } diff --git a/lib/babe/epoch_handler_integration_test.go b/lib/babe/epoch_handler_integration_test.go index f1c7d98774..cba6ac8c93 100644 --- a/lib/babe/epoch_handler_integration_test.go +++ b/lib/babe/epoch_handler_integration_test.go @@ -44,7 +44,14 @@ func TestEpochHandler_run_shouldReturnAfterContextCancel(t *testing.T) { startSlot := getCurrentSlot(slotDuration) handler := testHandleSlotFunc(t, authorityIndex, expectedEpoch, startSlot) - epochHandler, err := newEpochHandler(1, startSlot, epochData, testConstants, handler, aliceKeyPair) + epochDescriptor := &epochDescriptor{ + data: epochData, + startSlot: startSlot, + endSlot: startSlot + epochLength, + epoch: 1, + } + + epochHandler, err := newEpochHandler(epochDescriptor, testConstants, handler, aliceKeyPair) require.NoError(t, err) require.Equal(t, epochLength, uint64(len(epochHandler.slotToPreRuntimeDigest))) @@ -89,7 +96,14 @@ func TestEpochHandler_run(t *testing.T) { startSlot := getCurrentSlot(slotDuration) handler := testHandleSlotFunc(t, authorityIndex, expectedEpoch, startSlot) - epochHandler, err := newEpochHandler(1, startSlot, epochData, testConstants, handler, aliceKeyPair) + epochDescriptor := &epochDescriptor{ + data: epochData, + startSlot: startSlot, + endSlot: startSlot + epochLength, + epoch: 1, + } + + epochHandler, err := newEpochHandler(epochDescriptor, testConstants, handler, aliceKeyPair) require.NoError(t, err) require.Equal(t, epochLength, uint64(len(epochHandler.slotToPreRuntimeDigest))) diff --git a/lib/babe/epoch_handler_test.go b/lib/babe/epoch_handler_test.go index 11d62cf112..d3cbf580cf 100644 --- a/lib/babe/epoch_handler_test.go +++ b/lib/babe/epoch_handler_test.go @@ -35,12 +35,20 @@ func TestNewEpochHandler(t *testing.T) { keypair := keyring.Alice().(*sr25519.Keypair) - epochHandler, err := newEpochHandler(1, 9999, epochData, testConstants, testHandleSlotFunc, keypair) + startSlot := uint64(9999) + epochDescriptor := &epochDescriptor{ + data: epochData, + startSlot: startSlot, + endSlot: startSlot + testConstants.epochLength, + epoch: 1, + } + + epochHandler, err := newEpochHandler(epochDescriptor, testConstants, testHandleSlotFunc, keypair) require.NoError(t, err) require.Equal(t, 200, len(epochHandler.slotToPreRuntimeDigest)) - require.Equal(t, uint64(1), epochHandler.epochNumber) - require.Equal(t, uint64(9999), epochHandler.firstSlot) + require.Equal(t, uint64(1), epochHandler.descriptor.epoch) + require.Equal(t, uint64(9999), epochHandler.descriptor.startSlot) require.Equal(t, testConstants, epochHandler.constants) - require.Equal(t, epochData, epochHandler.epochData) + require.Equal(t, epochData, epochHandler.descriptor.data) require.NotNil(t, epochHandler.handleSlot) } diff --git a/lib/babe/epoch_integration_test.go b/lib/babe/epoch_integration_test.go index 5094ae0374..e797f2f596 100644 --- a/lib/babe/epoch_integration_test.go +++ b/lib/babe/epoch_integration_test.go @@ -17,29 +17,23 @@ import ( func TestInitiateEpoch_Epoch0(t *testing.T) { genesis, genesisTrie, genesisHeader := newWestendLocalGenesisWithTrieAndHeader(t) - babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, nil) + babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, AuthorOnEverySlotBABEConfig) babeService.constants.epochLength = 20 - startSlot := uint64(1000) - err := babeService.epochState.SetFirstSlot(startSlot) - require.NoError(t, err) - _, err = babeService.initiateEpoch(0) + epochDescriptor, err := babeService.initiateEpoch(0) require.NoError(t, err) - startSlot, err = babeService.epochState.GetStartSlotForEpoch(0) + epochStartSlot, err := babeService.epochState.GetStartSlotForEpoch(0, genesisHeader.Hash()) require.NoError(t, err) - require.Greater(t, startSlot, uint64(1)) + require.GreaterOrEqual(t, epochStartSlot, epochDescriptor.startSlot) } -func TestInitiateEpoch_Epoch1(t *testing.T) { +func TestInitiateEpoch_Epoch1And2(t *testing.T) { cfg := ServiceConfig{ Authority: true, } genesis, genesisTrie, genesisHeader := newWestendLocalGenesisWithTrieAndHeader(t) - babeService := createTestService(t, cfg, genesis, genesisTrie, genesisHeader, nil) - babeService.constants.epochLength = 10 - - state.AddBlocksToState(t, babeService.blockState.(*state.BlockState), 1, false) + babeService := createTestService(t, cfg, genesis, genesisTrie, genesisHeader, AuthorOnEverySlotBABEConfig) // epoch 1, check that genesis EpochData and ConfigData was properly set auth := types.AuthorityRaw{ @@ -49,60 +43,84 @@ func TestInitiateEpoch_Epoch1(t *testing.T) { data, err := babeService.epochState.GetEpochDataRaw(0, nil) require.NoError(t, err) + data.Authorities = []types.AuthorityRaw{auth} err = babeService.epochState.(*state.EpochState).SetEpochDataRaw(1, data) require.NoError(t, err) - ed, err := babeService.initiateEpoch(1) + const authorityIndex = 0 + currentEpochNumber := uint64(0) + ed, err := babeService.initiateEpoch(currentEpochNumber) require.NoError(t, err) expected := &epochData{ randomness: [32]byte{}, authorities: []types.AuthorityRaw{auth}, - authorityIndex: 0, - threshold: ed.threshold, + authorityIndex: authorityIndex, + threshold: ed.data.threshold, } - require.Equal(t, expected.randomness, ed.randomness) - require.Equal(t, expected.authorityIndex, ed.authorityIndex) - require.Equal(t, expected.threshold, ed.threshold) + require.Equal(t, expected.randomness, ed.data.randomness) + require.Equal(t, expected.authorityIndex, ed.data.authorityIndex) + require.Equal(t, expected.threshold, ed.data.threshold) - for i, auth := range ed.authorities { + preRuntimeDigest, err := claimSlot(ed.epoch, ed.startSlot, ed.data, babeService.keypair) + require.NoError(t, err) + + digest := types.NewDigest() + err = digest.Add(*preRuntimeDigest) + require.NoError(t, err) + + blockNumber1Header := state.AddBlockToState(t, + babeService.blockState.(*state.BlockState), 1, digest, genesisHeader.Hash()) + + for i, auth := range ed.data.authorities { require.Equal(t, expected.authorities[i], auth) } // for epoch 2, set EpochData but not ConfigData edata := &types.EpochDataRaw{ - Authorities: ed.authorities, + Authorities: ed.data.authorities, Randomness: [32]byte{9}, } - err = babeService.epochState.(*state.EpochState).SetEpochDataRaw(2, edata) + err = babeService.epochState.(*state.EpochState).SetEpochDataRaw(1, edata) require.NoError(t, err) - expected = &epochData{ + expectedEpoch1Data := &epochData{ randomness: edata.Randomness, authorities: edata.Authorities, authorityIndex: 0, - threshold: ed.threshold, + threshold: ed.data.threshold, } - ed, err = babeService.initiateEpoch(2) + currentEpochNumber = uint64(1) + ed, err = babeService.initiateEpoch(currentEpochNumber) require.NoError(t, err) - require.Equal(t, expected.randomness, ed.randomness) - require.Equal(t, expected.authorityIndex, ed.authorityIndex) - require.Equal(t, expected.threshold, ed.threshold) + require.Equal(t, expectedEpoch1Data.randomness, ed.data.randomness) + require.Equal(t, expectedEpoch1Data.authorityIndex, ed.data.authorityIndex) + require.Equal(t, expectedEpoch1Data.threshold, ed.data.threshold) - for i, auth := range ed.authorities { - require.Equal(t, expected.authorities[i], auth) + for i, auth := range ed.data.authorities { + require.Equal(t, expectedEpoch1Data.authorities[i], auth) } + preRuntimeDigest, err = claimSlot(ed.epoch, ed.startSlot, ed.data, babeService.keypair) + require.NoError(t, err) + + digest = types.NewDigest() + err = digest.Add(*preRuntimeDigest) + require.NoError(t, err) + + state.AddBlockToState(t, + babeService.blockState.(*state.BlockState), 2, digest, blockNumber1Header.Hash()) + // for epoch 3, set EpochData and ConfigData edata = &types.EpochDataRaw{ - Authorities: ed.authorities, + Authorities: ed.data.authorities, Randomness: [32]byte{9}, } - err = babeService.epochState.(*state.EpochState).SetEpochDataRaw(3, edata) + err = babeService.epochState.(*state.EpochState).SetEpochDataRaw(2, edata) require.NoError(t, err) cdata := &types.ConfigData{ @@ -110,26 +128,34 @@ func TestInitiateEpoch_Epoch1(t *testing.T) { C2: 99, } - err = babeService.epochState.(*state.EpochState).SetConfigData(3, cdata) + err = babeService.epochState.(*state.EpochState).StoreConfigData(2, cdata) require.NoError(t, err) threshold, err := CalculateThreshold(cdata.C1, cdata.C2, 1) require.NoError(t, err) - expected = &epochData{ - randomness: edata.Randomness, - authorities: edata.Authorities, - authorityIndex: 0, - threshold: threshold, + expectedEpochDescriptor := &epochDescriptor{ + data: &epochData{ + randomness: edata.Randomness, + authorities: edata.Authorities, + authorityIndex: 0, + threshold: threshold, + }, + epoch: 2, + startSlot: ed.endSlot, + endSlot: ed.endSlot + babeService.constants.epochLength, } - ed, err = babeService.initiateEpoch(3) + + currentEpochNumber = uint64(2) + ed, err = babeService.initiateEpoch(currentEpochNumber) + require.NoError(t, err) - require.Equal(t, expected, ed) + require.Equal(t, expectedEpochDescriptor, ed) } func TestIncrementEpoch(t *testing.T) { genesis, genesisTrie, genesisHeader := newWestendLocalGenesisWithTrieAndHeader(t) - bs := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, nil) + bs := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, AuthorOnEverySlotBABEConfig) next, err := bs.incrementEpoch() require.NoError(t, err) @@ -148,7 +174,10 @@ func TestService_getLatestEpochData_genesis(t *testing.T) { genesis, genesisTrie, genesisHeader := newWestendLocalGenesisWithTrieAndHeader(t) service, _, genesisCfg := newTestServiceSetupParameters(t, genesis, genesisTrie, genesisHeader) - latestEpochData, err := service.getLatestEpochData() + service.keypair = keyring.KeyAlice + service.authority = true + + latestEpochData, err := service.getEpochData(0, &genesisHeader) require.NoError(t, err) threshold, err := CalculateThreshold(genesisCfg.C1, genesisCfg.C2, len(genesisCfg.GenesisAuthorities)) @@ -163,7 +192,10 @@ func TestService_getLatestEpochData_epochData(t *testing.T) { genesis, genesisTrie, genesisHeader := newWestendLocalGenesisWithTrieAndHeader(t) service, epochState, genesisCfg := newTestServiceSetupParameters(t, genesis, genesisTrie, genesisHeader) - err := epochState.SetCurrentEpoch(1) + service.keypair = keyring.KeyAlice + service.authority = true + + err := epochState.StoreCurrentEpoch(1) require.NoError(t, err) data := &types.EpochDataRaw{ @@ -173,7 +205,7 @@ func TestService_getLatestEpochData_epochData(t *testing.T) { err = epochState.SetEpochDataRaw(1, data) require.NoError(t, err) - ed, err := service.getLatestEpochData() + ed, err := service.getEpochData(1, &genesisHeader) require.NoError(t, err) threshold, err := CalculateThreshold(genesisCfg.C1, genesisCfg.C2, len(data.Authorities)) require.NoError(t, err) @@ -186,7 +218,10 @@ func TestService_getLatestEpochData_configData(t *testing.T) { genesis, genesisTrie, genesisHeader := newWestendLocalGenesisWithTrieAndHeader(t) service, epochState, genesisCfg := newTestServiceSetupParameters(t, genesis, genesisTrie, genesisHeader) - err := epochState.SetCurrentEpoch(7) + service.keypair = keyring.KeyAlice + service.authority = true + + err := epochState.StoreCurrentEpoch(7) require.NoError(t, err) data := &types.EpochDataRaw{ @@ -200,10 +235,11 @@ func TestService_getLatestEpochData_configData(t *testing.T) { C1: 1, C2: 7, } - err = epochState.SetConfigData(1, cfgData) // set config data for a previous epoch, ensure latest config data is used + // set config data for a previous epoch, ensure latest config data is used + err = epochState.StoreConfigData(1, cfgData) require.NoError(t, err) - ed, err := service.getLatestEpochData() + ed, err := service.getEpochData(7, &genesisHeader) require.NoError(t, err) threshold, err := CalculateThreshold(cfgData.C1, cfgData.C2, len(data.Authorities)) require.NoError(t, err) diff --git a/lib/babe/epoch_test.go b/lib/babe/epoch_test.go index 2aae08d6f7..4dd877019c 100644 --- a/lib/babe/epoch_test.go +++ b/lib/babe/epoch_test.go @@ -16,60 +16,6 @@ import ( var keyring, _ = keystore.NewSr25519Keyring() -func TestBabeService_checkAndSetFirstSlot(t *testing.T) { - ctrl := gomock.NewController(t) - mockBlockState := NewMockBlockState(ctrl) - mockEpochState0 := NewMockEpochState(ctrl) - mockEpochState1 := NewMockEpochState(ctrl) - - mockEpochState0.EXPECT().GetStartSlotForEpoch(uint64(0)).Return(uint64(1), nil) - mockEpochState1.EXPECT().GetStartSlotForEpoch(uint64(0)).Return(uint64(99), nil) - mockEpochState1.EXPECT().SetFirstSlot(uint64(1)).Return(nil) - - testBabeSecondaryPlainPreDigest := types.BabeSecondaryPlainPreDigest{ - SlotNumber: 1, - } - - encDigest := newEncodedBabeDigest(t, testBabeSecondaryPlainPreDigest) - header := newTestHeader(t, *types.NewBABEPreRuntimeDigest(encDigest)) - - block := &types.Block{ - Header: *header, - } - - mockBlockState.EXPECT().GetBlockByNumber(uint(1)).Return(block, nil) - mockBlockState.EXPECT().GetBlockByNumber(uint(1)).Return(block, nil) - - bs0 := &Service{ - epochState: mockEpochState0, - blockState: mockBlockState, - } - - bs1 := &Service{ - epochState: mockEpochState1, - blockState: mockBlockState, - } - - cases := []struct { - name string - service *Service - }{ - { - name: "should not set first slot, as it's already set correctly", - service: bs0, - }, - { - name: "should update first slot, as it's set incorrectly", - service: bs1, - }, - } - - for _, tc := range cases { - err := tc.service.checkAndSetFirstSlot() - require.NoError(t, err) - } -} - func TestBabeService_getEpochDataAndStartSlot(t *testing.T) { kp := keyring.Alice().(*sr25519.Keypair) authority := types.NewAuthority(kp.Public(), uint64(1)) @@ -113,8 +59,12 @@ func TestBabeService_getEpochDataAndStartSlot(t *testing.T) { service: func(ctrl *gomock.Controller) *Service { mockEpochState := NewMockEpochState(ctrl) - mockEpochState.EXPECT().GetLatestEpochDataRaw().Return(testEpochDataEpoch0, nil) - mockEpochState.EXPECT().GetLatestConfigData().Return(testConfigData, nil) + mockEpochState.EXPECT(). + GetEpochDataRaw(uint64(0), nil). + Return(testEpochDataEpoch0, nil) + mockEpochState.EXPECT(). + GetConfigData(uint64(0), nil). + Return(testConfigData, nil) return &Service{ authority: true, diff --git a/lib/babe/helpers_test.go b/lib/babe/helpers_test.go index 5718545e48..3cfcc8ebb2 100644 --- a/lib/babe/helpers_test.go +++ b/lib/babe/helpers_test.go @@ -7,7 +7,6 @@ import ( "bytes" "path/filepath" "testing" - "time" "github.com/ChainSafe/gossamer/dot/core" "github.com/ChainSafe/gossamer/dot/state" @@ -26,6 +25,7 @@ import ( "github.com/ChainSafe/gossamer/lib/utils" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/ChainSafe/gossamer/pkg/trie" + "github.com/ChainSafe/gossamer/tests/utils/config" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" ) @@ -76,9 +76,10 @@ func newTestCoreService(t *testing.T, cfg *core.Config, genesis genesis.Genesis, telemetryMock.EXPECT().SendMessage(gomock.Any()).AnyTimes() config := state.Config{ - Path: testDatadirPath, - LogLevel: log.Info, - Telemetry: telemetryMock, + Path: testDatadirPath, + LogLevel: log.Info, + Telemetry: telemetryMock, + GenesisBABEConfig: config.BABEConfigurationTestDefault, } stateSrvc = state.NewService(config) @@ -178,9 +179,10 @@ func createTestService(t *testing.T, cfg ServiceConfig, genesis genesis.Genesis, testDatadirPath := t.TempDir() config := state.Config{ - Path: testDatadirPath, - LogLevel: log.Info, - Telemetry: telemetryMock, + Path: testDatadirPath, + LogLevel: log.Info, + Telemetry: telemetryMock, + GenesisBABEConfig: babeConfig, } dbSrv := state.NewService(config) dbSrv.UseMemDB() @@ -264,15 +266,26 @@ func newTestServiceSetupParameters(t *testing.T, genesis genesis.Genesis, testDatadirPath := t.TempDir() + rtCfg := wazero_runtime.Config{ + Storage: rtstorage.NewTrieState(genesisTrie), + } + + rt, err := wazero_runtime.NewRuntimeFromGenesis(rtCfg) + require.NoError(t, err) + + genCfg, err := rt.BabeConfiguration() + require.NoError(t, err) + config := state.Config{ - Path: testDatadirPath, - LogLevel: log.Info, - Telemetry: telemetryMock, + Path: testDatadirPath, + LogLevel: log.Info, + Telemetry: telemetryMock, + GenesisBABEConfig: genCfg, } dbSrv := state.NewService(config) dbSrv.UseMemDB() - err := dbSrv.Initialise(&genesis, &genesisHeader, genesisTrie) + err = dbSrv.Initialise(&genesis, &genesisHeader, genesisTrie) require.NoError(t, err) err = dbSrv.Start() @@ -282,16 +295,6 @@ func newTestServiceSetupParameters(t *testing.T, genesis genesis.Genesis, _ = dbSrv.Stop() }) - rtCfg := wazero_runtime.Config{ - Storage: rtstorage.NewTrieState(genesisTrie), - } - - rt, err := wazero_runtime.NewRuntimeFromGenesis(rtCfg) - require.NoError(t, err) - - genCfg, err := rt.BabeConfiguration() - require.NoError(t, err) - s := &Service{ epochState: dbSrv.Epoch, } @@ -326,21 +329,8 @@ func buildLocalTransaction(t *testing.T, rt runtime.Instance, ext types.Extrinsi return types.Extrinsic(bytes.Join(extrinsicParts, nil)) } -func getSlot(t *testing.T, rt runtime.Instance, timestamp time.Time) Slot { - t.Helper() - babeConfig, err := rt.BabeConfiguration() - require.NoError(t, err) - - currentSlot := uint64(timestamp.UnixMilli()) / babeConfig.SlotDuration - return Slot{ - start: timestamp, - duration: time.Duration(babeConfig.SlotDuration) * time.Millisecond, - number: currentSlot, - } -} - func createTestBlockWithSlot(t *testing.T, babeService *Service, parent *types.Header, - exts [][]byte, epoch uint64, epochData *epochData, slot Slot) *types.Block { + exts [][]byte, epochDescriptor *epochDescriptor, slot Slot) *types.Block { for _, ext := range exts { validTransaction := transaction.NewValidTransaction(ext, &transaction.Validity{}) _, err := babeService.transactionState.Push(validTransaction) @@ -351,10 +341,10 @@ func createTestBlockWithSlot(t *testing.T, babeService *Service, parent *types.H rt, err := babeService.blockState.GetRuntime(bestBlockHash) require.NoError(t, err) - preRuntimeDigest, err := claimSlot(epoch, slot.number, epochData, babeService.keypair) + preRuntimeDigest, err := claimSlot(epochDescriptor.epoch, slot.number, epochDescriptor.data, babeService.keypair) require.NoError(t, err) - block, err := babeService.buildBlock(parent, slot, rt, epochData.authorityIndex, preRuntimeDigest) + block, err := babeService.buildBlock(parent, slot, rt, epochDescriptor.data.authorityIndex, preRuntimeDigest) require.NoError(t, err) babeService.blockState.(*state.BlockState).StoreRuntime(block.Header.Hash(), rt) diff --git a/lib/babe/mock_state_test.go b/lib/babe/mock_state_test.go index ab5e132028..e1571cf0da 100644 --- a/lib/babe/mock_state_test.go +++ b/lib/babe/mock_state_test.go @@ -113,6 +113,21 @@ func (mr *MockBlockStateMockRecorder) GenesisHash() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GenesisHash", reflect.TypeOf((*MockBlockState)(nil).GenesisHash)) } +// 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 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlockByHash", reflect.TypeOf((*MockBlockState)(nil).GetBlockByHash), arg0) +} + // GetBlockByNumber mocks base method. func (m *MockBlockState) GetBlockByNumber(arg0 uint) (*types.Block, error) { m.ctrl.T.Helper() @@ -491,12 +506,11 @@ func (mr *MockEpochStateMockRecorder) GetEpochForBlock(arg0 any) *gomock.Call { } // GetEpochLength mocks base method. -func (m *MockEpochState) GetEpochLength() (uint64, error) { +func (m *MockEpochState) GetEpochLength() uint64 { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetEpochLength") ret0, _ := ret[0].(uint64) - ret1, _ := ret[1].(error) - return ret0, ret1 + return ret0 } // GetEpochLength indicates an expected call of GetEpochLength. @@ -551,46 +565,18 @@ func (mr *MockEpochStateMockRecorder) GetSlotDuration() *gomock.Call { } // GetStartSlotForEpoch mocks base method. -func (m *MockEpochState) GetStartSlotForEpoch(arg0 uint64) (uint64, error) { +func (m *MockEpochState) GetStartSlotForEpoch(arg0 uint64, arg1 common.Hash) (uint64, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetStartSlotForEpoch", arg0) + ret := m.ctrl.Call(m, "GetStartSlotForEpoch", arg0, arg1) ret0, _ := ret[0].(uint64) ret1, _ := ret[1].(error) return ret0, ret1 } // GetStartSlotForEpoch indicates an expected call of GetStartSlotForEpoch. -func (mr *MockEpochStateMockRecorder) GetStartSlotForEpoch(arg0 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStartSlotForEpoch", reflect.TypeOf((*MockEpochState)(nil).GetStartSlotForEpoch), 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 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCurrentEpoch", reflect.TypeOf((*MockEpochState)(nil).SetCurrentEpoch), arg0) -} - -// 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 any) *gomock.Call { +func (mr *MockEpochStateMockRecorder) GetStartSlotForEpoch(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetFirstSlot", reflect.TypeOf((*MockEpochState)(nil).SetFirstSlot), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStartSlotForEpoch", reflect.TypeOf((*MockEpochState)(nil).GetStartSlotForEpoch), arg0, arg1) } // SkipVerify mocks base method. @@ -608,6 +594,20 @@ func (mr *MockEpochStateMockRecorder) SkipVerify(arg0 any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SkipVerify", reflect.TypeOf((*MockEpochState)(nil).SkipVerify), arg0) } +// StoreCurrentEpoch mocks base method. +func (m *MockEpochState) StoreCurrentEpoch(arg0 uint64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StoreCurrentEpoch", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// StoreCurrentEpoch indicates an expected call of StoreCurrentEpoch. +func (mr *MockEpochStateMockRecorder) StoreCurrentEpoch(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StoreCurrentEpoch", reflect.TypeOf((*MockEpochState)(nil).StoreCurrentEpoch), arg0) +} + // MockBlockImportHandler is a mock of BlockImportHandler interface. type MockBlockImportHandler struct { ctrl *gomock.Controller diff --git a/lib/babe/state.go b/lib/babe/state.go index f4aee6f4d4..a112f7d812 100644 --- a/lib/babe/state.go +++ b/lib/babe/state.go @@ -33,6 +33,7 @@ type BlockState interface { NumberIsFinalised(blockNumber uint) (bool, error) GetRuntime(blockHash common.Hash) (runtime runtime.Instance, err error) StoreRuntime(common.Hash, runtime.Instance) + GetBlockByHash(common.Hash) (*types.Block, error) ImportedBlockNotifierManager } @@ -56,18 +57,17 @@ type TransactionState interface { // EpochState is the interface for epoch methods type EpochState interface { - GetEpochLength() (uint64, error) + GetEpochLength() uint64 GetSlotDuration() (time.Duration, error) - SetCurrentEpoch(epoch uint64) error + StoreCurrentEpoch(epoch uint64) error GetCurrentEpoch() (uint64, error) GetEpochDataRaw(epoch uint64, header *types.Header) (*types.EpochDataRaw, error) GetConfigData(epoch uint64, header *types.Header) (*types.ConfigData, error) GetLatestConfigData() (*types.ConfigData, error) - GetStartSlotForEpoch(epoch uint64) (uint64, error) + GetStartSlotForEpoch(epoch uint64, bestBlockHash common.Hash) (uint64, error) GetEpochForBlock(header *types.Header) (uint64, error) - SetFirstSlot(slot uint64) error GetLatestEpochDataRaw() (*types.EpochDataRaw, error) SkipVerify(*types.Header) (bool, error) } diff --git a/lib/babe/verify.go b/lib/babe/verify.go index 36d796465f..634ba0119d 100644 --- a/lib/babe/verify.go +++ b/lib/babe/verify.go @@ -133,29 +133,6 @@ func (v *VerificationManager) VerifyBlock(header *types.Header) error { has bool ) - // 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 == 1 { - block1IsFinal, err := v.blockState.NumberIsFinalised(header.Number) - if err != nil { - return fmt.Errorf("failed to check if block 1 is finalised: %w", err) - } - - if !block1IsFinal { - firstSlot, err := types.GetSlotFromHeader(header) - if err != nil { - return fmt.Errorf("failed to get slot from header of block 1: %w", err) - } - - logger.Debugf("syncing block 1, setting first slot as %d", firstSlot) - - err = v.epochState.SetFirstSlot(firstSlot) - if err != nil { - return fmt.Errorf("failed to set current epoch after receiving block 1: %w", err) - } - } - } - epoch, err := v.epochState.GetEpochForBlock(header) if err != nil { return fmt.Errorf("failed to get epoch for block header: %w", err) diff --git a/lib/babe/verify_integration_test.go b/lib/babe/verify_integration_test.go index 1b23ad332b..b579e49e7b 100644 --- a/lib/babe/verify_integration_test.go +++ b/lib/babe/verify_integration_test.go @@ -16,9 +16,9 @@ import ( "github.com/ChainSafe/gossamer/dot/telemetry" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/internal/database" - "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/crypto/sr25519" "github.com/ChainSafe/gossamer/pkg/scale" + "github.com/ChainSafe/gossamer/tests/utils/config" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" @@ -26,7 +26,7 @@ import ( func TestVerificationManager_OnDisabled_InvalidIndex(t *testing.T) { genesis, genesisTrie, genesisHeader := newWestendDevGenesisWithTrieAndHeader(t) - babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, nil) + babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, AuthorOnEverySlotBABEConfig) db, err := database.NewPebble(t.TempDir(), true) require.NoError(t, err) @@ -34,15 +34,15 @@ func TestVerificationManager_OnDisabled_InvalidIndex(t *testing.T) { slotState := state.NewSlotState(db) vm := NewVerificationManager(babeService.blockState, slotState, babeService.epochState) - bestBlockHash := babeService.blockState.BestBlockHash() - runtime, err := babeService.blockState.GetRuntime(bestBlockHash) + epochDescriptor, err := babeService.initiateEpoch(testEpochIndex) require.NoError(t, err) - epochData, err := babeService.initiateEpoch(testEpochIndex) - require.NoError(t, err) - - slot := getSlot(t, runtime, time.Now()) - block := createTestBlockWithSlot(t, babeService, emptyHeader, [][]byte{}, testEpochIndex, epochData, slot) + slot := Slot{ + start: getSlotStartTime(epochDescriptor.startSlot, babeService.constants.slotDuration), + duration: babeService.constants.slotDuration, + number: epochDescriptor.startSlot, + } + block := createTestBlockWithSlot(t, babeService, emptyHeader, [][]byte{}, epochDescriptor, slot) err = vm.SetOnDisabled(1, &block.Header) require.Equal(t, ErrInvalidBlockProducerIndex, err) @@ -50,7 +50,7 @@ func TestVerificationManager_OnDisabled_InvalidIndex(t *testing.T) { func TestVerificationManager_OnDisabled_NewDigest(t *testing.T) { genesis, genesisTrie, genesisHeader := newWestendDevGenesisWithTrieAndHeader(t) - babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, nil) + babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, AuthorOnEverySlotBABEConfig) db, err := database.NewPebble(t.TempDir(), true) require.NoError(t, err) @@ -58,23 +58,23 @@ func TestVerificationManager_OnDisabled_NewDigest(t *testing.T) { vm := NewVerificationManager(babeService.blockState, slotState, babeService.epochState) - bestBlockHash := babeService.blockState.BestBlockHash() - runtime, err := babeService.blockState.GetRuntime(bestBlockHash) - require.NoError(t, err) - - epochData, err := babeService.initiateEpoch(testEpochIndex) + epochDescriptor, err := babeService.initiateEpoch(testEpochIndex) require.NoError(t, err) vm.epochInfo[testEpochIndex] = &verifierInfo{ - authorities: epochData.authorities, - threshold: epochData.threshold, - randomness: epochData.randomness, + authorities: epochDescriptor.data.authorities, + threshold: epochDescriptor.data.threshold, + randomness: epochDescriptor.data.randomness, } parent, _ := babeService.blockState.BestBlockHeader() - slot := getSlot(t, runtime, time.Now()) - block := createTestBlockWithSlot(t, babeService, &genesisHeader, [][]byte{}, testEpochIndex, epochData, slot) + slot := Slot{ + start: getSlotStartTime(epochDescriptor.startSlot, babeService.constants.slotDuration), + duration: babeService.constants.slotDuration, + number: epochDescriptor.startSlot, + } + block := createTestBlockWithSlot(t, babeService, &genesisHeader, [][]byte{}, epochDescriptor, slot) err = vm.blockState.AddBlock(block) require.NoError(t, err) @@ -82,8 +82,12 @@ func TestVerificationManager_OnDisabled_NewDigest(t *testing.T) { require.NoError(t, err) // create an OnDisabled change on a different branch - slot2 := getSlot(t, runtime, time.Now()) - block = createTestBlockWithSlot(t, babeService, parent, [][]byte{}, testEpochIndex, epochData, slot2) + slot2 := Slot{ + start: getSlotStartTime(epochDescriptor.startSlot, babeService.constants.slotDuration), + duration: babeService.constants.slotDuration, + number: epochDescriptor.startSlot, + } + block = createTestBlockWithSlot(t, babeService, parent, [][]byte{}, epochDescriptor, slot2) err = vm.blockState.AddBlock(block) require.NoError(t, err) @@ -93,13 +97,9 @@ func TestVerificationManager_OnDisabled_NewDigest(t *testing.T) { func TestVerificationManager_OnDisabled_DuplicateDigest(t *testing.T) { genesis, genesisTrie, genesisHeader := newWestendDevGenesisWithTrieAndHeader(t) - babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, nil) + babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, AuthorOnEverySlotBABEConfig) - bestBlockHash := babeService.blockState.BestBlockHash() - runtime, err := babeService.blockState.GetRuntime(bestBlockHash) - require.NoError(t, err) - - epochData, err := babeService.initiateEpoch(testEpochIndex) + epochDescriptor, err := babeService.initiateEpoch(testEpochIndex) require.NoError(t, err) db, err := database.NewPebble(t.TempDir(), true) @@ -108,13 +108,17 @@ func TestVerificationManager_OnDisabled_DuplicateDigest(t *testing.T) { vm := NewVerificationManager(babeService.blockState, slotState, babeService.epochState) vm.epochInfo[testEpochIndex] = &verifierInfo{ - authorities: epochData.authorities, - threshold: epochData.threshold, - randomness: epochData.randomness, + authorities: epochDescriptor.data.authorities, + threshold: epochDescriptor.data.threshold, + randomness: epochDescriptor.data.randomness, } - slot := getSlot(t, runtime, time.Now()) - block := createTestBlockWithSlot(t, babeService, &genesisHeader, [][]byte{}, testEpochIndex, epochData, slot) + slot := Slot{ + start: getSlotStartTime(epochDescriptor.startSlot, babeService.constants.slotDuration), + duration: babeService.constants.slotDuration, + number: epochDescriptor.startSlot, + } + block := createTestBlockWithSlot(t, babeService, &genesisHeader, [][]byte{}, epochDescriptor, slot) err = vm.blockState.AddBlock(block) require.NoError(t, err) @@ -122,8 +126,12 @@ func TestVerificationManager_OnDisabled_DuplicateDigest(t *testing.T) { require.NoError(t, err) // create an OnDisabled change on a different branch - slot2 := getSlot(t, runtime, time.Now()) - block2 := createTestBlockWithSlot(t, babeService, &block.Header, [][]byte{}, testEpochIndex, epochData, slot2) + slot2 := Slot{ + start: getSlotStartTime(epochDescriptor.startSlot, babeService.constants.slotDuration), + duration: babeService.constants.slotDuration, + number: epochDescriptor.startSlot, + } + block2 := createTestBlockWithSlot(t, babeService, &block.Header, [][]byte{}, epochDescriptor, slot2) err = vm.blockState.AddBlock(block2) require.NoError(t, err) @@ -131,10 +139,26 @@ func TestVerificationManager_OnDisabled_DuplicateDigest(t *testing.T) { require.Equal(t, ErrAuthorityAlreadyDisabled, err) } -// TODO Rather than test error, test happy path #3060 func TestVerificationManager_VerifyBlock_Secondary(t *testing.T) { genesis, genesisTrie, genesisHeader := newWestendDevGenesisWithTrieAndHeader(t) - babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, nil) + + babeCfgWithSecondarySlots := config.BABEConfigurationTestDefault + // these parameters will decrease the probability + // of a primary author claiming which will makes us + // propose a secondary block + babeCfgWithSecondarySlots.C1 = 1 + babeCfgWithSecondarySlots.C2 = 9000 + babeCfgWithSecondarySlots.SecondarySlots = 1 + babeCfgWithSecondarySlots.GenesisAuthorities = []types.AuthorityRaw{ + { + Key: keyring.Alice().Public().(*sr25519.PublicKey).AsBytes(), + Weight: 1, + }, + } + + babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, + genesisHeader, babeCfgWithSecondarySlots) + babeService.authority = true db, err := database.NewPebble(t.TempDir(), true) require.NoError(t, err) @@ -142,9 +166,15 @@ func TestVerificationManager_VerifyBlock_Secondary(t *testing.T) { vm := NewVerificationManager(babeService.blockState, slotState, babeService.epochState) + const epoch = 0 + epochDescriptor, err := babeService.initiateEpoch(epoch) + require.NoError(t, err) + + const authIndex = 0 secondaryDigest := createSecondaryVRFPreDigest(t, keyring.Alice().(*sr25519.Keypair), - 0, uint64(0), uint64(0), Randomness{}) + authIndex, epochDescriptor.startSlot, epochDescriptor.epoch, epochDescriptor.data.randomness) babeDigest := types.NewBabeDigest() + // NOTE: I think this was get encoded incorrectly before the VDT interface change. // *types.BabeSecondaryVRFPreDigest was being passed in and encoded later err = babeDigest.SetValue(*secondaryDigest) @@ -160,7 +190,6 @@ func TestVerificationManager_VerifyBlock_Secondary(t *testing.T) { } // create new block header - const number uint = 1 digest := types.NewDigest() err = digest.Add(*preDigest) require.NoError(t, err) @@ -171,14 +200,16 @@ func TestVerificationManager_VerifyBlock_Secondary(t *testing.T) { Data: []byte{0}, } require.NoError(t, err) - err = digest.Add(*seal) require.NoError(t, err) - header := types.NewHeader(common.Hash{}, common.Hash{}, common.Hash{}, number, digest) block := types.Block{ - Header: *header, - Body: nil, + Header: types.Header{ + Number: 1, + ParentHash: genesisHeader.Hash(), + Digest: digest, + }, + Body: nil, } err = vm.VerifyBlock(&block.Header) require.EqualError(t, err, "invalid signature length") @@ -187,7 +218,7 @@ func TestVerificationManager_VerifyBlock_Secondary(t *testing.T) { func TestVerificationManager_VerifyBlock_CurrentEpoch(t *testing.T) { t.Parallel() genesis, genesisTrie, genesisHeader := newWestendDevGenesisWithTrieAndHeader(t) - babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, nil) + babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, AuthorOnEverySlotBABEConfig) db, err := database.NewPebble(t.TempDir(), true) require.NoError(t, err) @@ -195,37 +226,35 @@ func TestVerificationManager_VerifyBlock_CurrentEpoch(t *testing.T) { vm := NewVerificationManager(babeService.blockState, slotState, babeService.epochState) - bestBlockHash := babeService.blockState.BestBlockHash() - runtime, err := babeService.blockState.GetRuntime(bestBlockHash) - require.NoError(t, err) - - epochData, err := babeService.initiateEpoch(0) + epochDescriptor, err := babeService.initiateEpoch(0) require.NoError(t, err) - slot := getSlot(t, runtime, time.Unix(6, 0)) - block := createTestBlockWithSlot(t, babeService, &genesisHeader, [][]byte{}, 0, epochData, slot) + slot := Slot{ + start: getSlotStartTime(epochDescriptor.startSlot, babeService.constants.slotDuration), + duration: babeService.constants.slotDuration, + number: epochDescriptor.startSlot, + } + block := createTestBlockWithSlot(t, babeService, &genesisHeader, [][]byte{}, epochDescriptor, slot) err = vm.VerifyBlock(&block.Header) require.NoError(t, err) } func TestVerificationManager_VerifyBlock_FutureEpoch(t *testing.T) { + t.Skip("TODO: move this test under TestVerificationManager_VerifyBlock_MultipleEpochs") + t.Parallel() auth := types.Authority{ Key: keyring.Alice().(*sr25519.Keypair).Public(), Weight: 1, } - babeConfig := &types.BabeConfiguration{ - SlotDuration: 6000, - EpochLength: 600, - C1: 1, - C2: 4, - GenesisAuthorities: []types.AuthorityRaw{*auth.ToRaw()}, - Randomness: [32]byte{}, - SecondarySlots: 1, - } + defaultBabeConfiguration := config.BABEConfigurationTestDefault + defaultBabeConfiguration.GenesisAuthorities = []types.AuthorityRaw{*auth.ToRaw()} + defaultBabeConfiguration.SecondarySlots = 1 + genesis, genesisTrie, genesisHeader := newWestendDevGenesisWithTrieAndHeader(t) - babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, babeConfig) + babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, + genesisHeader, defaultBabeConfiguration) db, err := database.NewPebble(t.TempDir(), true) require.NoError(t, err) @@ -233,10 +262,6 @@ func TestVerificationManager_VerifyBlock_FutureEpoch(t *testing.T) { verificationManager := NewVerificationManager(babeService.blockState, slotState, babeService.epochState) - bestBlockHash := babeService.blockState.BestBlockHash() - runtime, err := babeService.blockState.GetRuntime(bestBlockHash) - require.NoError(t, err) - const futureEpoch = uint64(2) err = babeService.epochState.(*state.EpochState).SetEpochDataRaw(futureEpoch, &types.EpochDataRaw{ Authorities: []types.AuthorityRaw{{ @@ -245,15 +270,16 @@ func TestVerificationManager_VerifyBlock_FutureEpoch(t *testing.T) { }) require.NoError(t, err) - futureEpochData, err := babeService.initiateEpoch(futureEpoch) + futureEpochDescriptor, err := babeService.initiateEpoch(futureEpoch) require.NoError(t, err) - futureEpochSlotNumber := int64(babeService.EpochLength()*futureEpoch+1) * 6 - futureTimestamp := time.Unix(futureEpochSlotNumber, 0) - - slot := getSlot(t, runtime, futureTimestamp) + slot := Slot{ + start: getSlotStartTime(futureEpochDescriptor.startSlot, babeService.constants.slotDuration), + duration: babeService.constants.slotDuration, + number: futureEpochDescriptor.startSlot, + } slot.number = babeService.EpochLength()*futureEpoch + 1 - block := createTestBlockWithSlot(t, babeService, &genesisHeader, [][]byte{}, futureEpoch, futureEpochData, slot) + block := createTestBlockWithSlot(t, babeService, &genesisHeader, [][]byte{}, futureEpochDescriptor, slot) err = verificationManager.VerifyBlock(&block.Header) require.NoError(t, err) @@ -269,53 +295,70 @@ func TestVerificationManager_VerifyBlock_MultipleEpochs(t *testing.T) { SlotDuration: 6000, EpochLength: 600, C1: 1, - C2: 4, + C2: 1, GenesisAuthorities: []types.AuthorityRaw{*auth.ToRaw()}, Randomness: [32]byte{}, SecondarySlots: 1, } + genesis, genesisTrie, genesisHeader := newWestendDevGenesisWithTrieAndHeader(t) - babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, babeConfig) + babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, + genesisHeader, babeConfig) db, err := database.NewPebble(t.TempDir(), true) require.NoError(t, err) slotState := state.NewSlotState(db) - verificationManager := NewVerificationManager(babeService.blockState, slotState, babeService.epochState) - bestBlockHash := babeService.blockState.BestBlockHash() - runtime, err := babeService.blockState.GetRuntime(bestBlockHash) + const epoch = uint64(0) + epochDescriptor, err := babeService.initiateEpoch(epoch) require.NoError(t, err) - const futureEpoch = uint64(2) - err = babeService.epochState.(*state.EpochState).SetEpochDataRaw(futureEpoch, &types.EpochDataRaw{ - Authorities: []types.AuthorityRaw{{ - Key: [32]byte(keyring.Alice().(*sr25519.Keypair).Public().Encode()), - }}, - }) - require.NoError(t, err) + slot := Slot{ + start: getSlotStartTime(epochDescriptor.startSlot, babeService.constants.slotDuration), + duration: babeService.constants.slotDuration, + number: epochDescriptor.startSlot, + } - futureEpochData, err := babeService.initiateEpoch(futureEpoch) + blockNumber01 := createTestBlockWithSlot(t, babeService, &genesisHeader, [][]byte{}, epochDescriptor, slot) + err = verificationManager.VerifyBlock(&blockNumber01.Header) require.NoError(t, err) - futureEpochSlotNumber := int64(babeService.EpochLength()*futureEpoch+1) * 6 - futureTimestamp := time.Unix(futureEpochSlotNumber, 0) - - futureSlot := getSlot(t, runtime, futureTimestamp) - futureSlot.number = babeService.EpochLength()*futureEpoch + 1 - block := createTestBlockWithSlot(t, babeService, &genesisHeader, [][]byte{}, futureEpoch, futureEpochData, futureSlot) - - err = verificationManager.VerifyBlock(&block.Header) + err = babeService.blockState.(*state.BlockState).AddBlock(blockNumber01) require.NoError(t, err) - epochData, err := babeService.initiateEpoch(0) + futureEpoch := uint64(1) + err = babeService.epochState.(*state.EpochState).SetEpochDataRaw(futureEpoch, &types.EpochDataRaw{ + Randomness: [32]byte{9}, + Authorities: []types.AuthorityRaw{ + { + Key: [32]byte(keyring.Bob().(*sr25519.Keypair).Public().Encode()), + Weight: 1, + }, + { + Key: [32]byte(keyring.Alice().(*sr25519.Keypair).Public().Encode()), + Weight: 1, + }, + }, + }) require.NoError(t, err) - slot := getSlot(t, runtime, time.Now()) - block = createTestBlockWithSlot(t, babeService, &genesisHeader, [][]byte{}, 0, epochData, slot) + futureEpochDescriptor, err := babeService.initiateEpoch(futureEpoch) + require.NoError(t, err) - err = verificationManager.VerifyBlock(&block.Header) + futureSlot := Slot{ + start: getSlotStartTime(futureEpochDescriptor.startSlot, babeService.constants.slotDuration), + duration: babeService.constants.slotDuration, + number: futureEpochDescriptor.startSlot, + } + blockNumber02 := createTestBlockWithSlot(t, babeService, + &blockNumber01.Header, [][]byte{}, futureEpochDescriptor, futureSlot) + err = verificationManager.VerifyBlock(&blockNumber02.Header) require.NoError(t, err) + + // TODO: include test to verify skipped epoch + // skip the epoch 2 and initiate epoch 3, we should use epoch data that were + // meant to be used by epoch 2 } func TestVerificationManager_VerifyBlock_InvalidBlockOverThreshold(t *testing.T) { @@ -324,11 +367,12 @@ func TestVerificationManager_VerifyBlock_InvalidBlockOverThreshold(t *testing.T) Key: keyring.Alice().(*sr25519.Keypair).Public(), Weight: 1, } + babeConfig := &types.BabeConfiguration{ SlotDuration: 6000, EpochLength: 600, C1: 1, - C2: 4, + C2: 9000, GenesisAuthorities: []types.AuthorityRaw{*auth.ToRaw()}, Randomness: [32]byte{}, SecondarySlots: 0, @@ -343,22 +387,18 @@ func TestVerificationManager_VerifyBlock_InvalidBlockOverThreshold(t *testing.T) vm := NewVerificationManager(babeService.blockState, slotState, babeService.epochState) - epochData, err := babeService.initiateEpoch(testEpochIndex) + const epoch = 0 + epochDescriptor, err := babeService.initiateEpoch(epoch) require.NoError(t, err) - bestBlockHash := babeService.blockState.BestBlockHash() - runtime, err := babeService.blockState.GetRuntime(bestBlockHash) - require.NoError(t, err) + epochDescriptor.data.threshold = maxThreshold - epochData.threshold = maxThreshold - - // slots are 6 seconds on westend and using time.Now() allows us to create a block at any point in the slot. - // So we need to manually set time to produce consistent results. See here: - // https://github.com/paritytech/substrate/blob/09de7b41599add51cf27eca8f1bc4c50ed8e9453/frame/timestamp/src/lib.rs#L229 - // https://github.com/paritytech/substrate/blob/09de7b41599add51cf27eca8f1bc4c50ed8e9453/frame/timestamp/src/lib.rs#L206 - timestamp := time.Unix(6, 0) - slot := getSlot(t, runtime, timestamp) - block := createTestBlockWithSlot(t, babeService, &genesisHeader, [][]byte{}, testEpochIndex, epochData, slot) + slot := Slot{ + start: getSlotStartTime(epochDescriptor.startSlot, babeService.constants.slotDuration), + duration: babeService.constants.slotDuration, + number: epochDescriptor.startSlot, + } + block := createTestBlockWithSlot(t, babeService, &genesisHeader, [][]byte{}, epochDescriptor, slot) block.Header.Hash() err = babeService.blockState.AddBlock(block) @@ -370,9 +410,8 @@ func TestVerificationManager_VerifyBlock_InvalidBlockOverThreshold(t *testing.T) func TestVerificationManager_VerifyBlock_InvalidBlockAuthority(t *testing.T) { genesis, genesisTrie, genesisHeader := newWestendDevGenesisWithTrieAndHeader(t) - babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, nil) + babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, AuthorOnEverySlotBABEConfig) - // Create service with no authorities babeConfig := &types.BabeConfiguration{ SlotDuration: 6000, EpochLength: 600, @@ -382,8 +421,10 @@ func TestVerificationManager_VerifyBlock_InvalidBlockAuthority(t *testing.T) { Randomness: [32]byte{}, SecondarySlots: 0, } + genesisBob, genesisTrieBob, genesisHeaderBob := newWestendDevGenesisWithTrieAndHeader(t) - babeServiceBob := createTestService(t, ServiceConfig{}, genesisBob, genesisTrieBob, genesisHeaderBob, babeConfig) + babeServiceBob := createTestService(t, ServiceConfig{}, genesisBob, genesisTrieBob, + genesisHeaderBob, babeConfig) db, err := database.NewPebble(t.TempDir(), true) require.NoError(t, err) @@ -391,15 +432,15 @@ func TestVerificationManager_VerifyBlock_InvalidBlockAuthority(t *testing.T) { verificationManager := NewVerificationManager(babeServiceBob.blockState, slotState, babeServiceBob.epochState) - bestBlockHash := babeService.blockState.BestBlockHash() - runtime, err := babeService.blockState.GetRuntime(bestBlockHash) - require.NoError(t, err) - - epochData, err := babeService.initiateEpoch(testEpochIndex) + epochDescriptor, err := babeService.initiateEpoch(testEpochIndex) require.NoError(t, err) - slot := getSlot(t, runtime, time.Now()) - block := createTestBlockWithSlot(t, babeServiceBob, &genesisHeader, [][]byte{}, testEpochIndex, epochData, slot) + slot := Slot{ + start: getSlotStartTime(epochDescriptor.startSlot, babeService.constants.slotDuration), + duration: babeService.constants.slotDuration, + number: epochDescriptor.startSlot, + } + block := createTestBlockWithSlot(t, babeServiceBob, &genesisHeader, [][]byte{}, epochDescriptor, slot) err = verificationManager.VerifyBlock(&block.Header) require.Equal(t, ErrInvalidBlockProducerIndex, errors.Unwrap(err)) @@ -410,27 +451,22 @@ func TestVerifyPrimarySlotWinner(t *testing.T) { Key: keyring.Alice().(*sr25519.Keypair).Public(), Weight: 1, } - babeConfig := &types.BabeConfiguration{ - SlotDuration: 6000, - EpochLength: 600, - C1: 1, - C2: 4, - GenesisAuthorities: []types.AuthorityRaw{*auth.ToRaw()}, - Randomness: [32]byte{}, - SecondarySlots: 1, - } + babeConfig := config.BABEConfigurationTestDefault + babeConfig.SecondarySlots = 1 + babeConfig.GenesisAuthorities = []types.AuthorityRaw{*auth.ToRaw()} + genesis, genesisTrie, genesisHeader := newWestendLocalGenesisWithTrieAndHeader(t) babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, babeConfig) - epochData, err := babeService.initiateEpoch(0) + epochDescriptor, err := babeService.initiateEpoch(0) require.NoError(t, err) // create proof that we can authorize this block - epochData.threshold = maxThreshold - epochData.authorityIndex = 0 + epochDescriptor.data.threshold = maxThreshold + epochDescriptor.data.authorityIndex = 0 const slotNumber uint64 = 1 - preRuntimeDigest, err := claimSlot(testEpochIndex, slotNumber, epochData, babeService.keypair) + preRuntimeDigest, err := claimSlot(testEpochIndex, slotNumber, epochDescriptor.data, babeService.keypair) require.NoError(t, err) babePreDigest, err := types.DecodeBabePreDigest(preRuntimeDigest.Data) @@ -444,9 +480,9 @@ func TestVerifyPrimarySlotWinner(t *testing.T) { slotState := state.NewSlotState(db) verifier := newVerifier(babeService.blockState, slotState, testEpochIndex, &verifierInfo{ - authorities: epochData.authorities, - threshold: epochData.threshold, - randomness: epochData.randomness, + authorities: epochDescriptor.data.authorities, + threshold: epochDescriptor.data.threshold, + randomness: epochDescriptor.data.randomness, }, time.Second) ok, err = verifier.verifyPrimarySlotWinner(digest.AuthorityIndex, slotNumber, digest.VRFOutput, digest.VRFProof) @@ -459,27 +495,27 @@ func TestVerifyAuthorshipRight(t *testing.T) { Authority: true, } genesis, genesisTrie, genesisHeader := newWestendLocalGenesisWithTrieAndHeader(t) - babeService := createTestService(t, serviceConfig, genesis, genesisTrie, genesisHeader, nil) + babeService := createTestService(t, serviceConfig, genesis, genesisTrie, genesisHeader, AuthorOnEverySlotBABEConfig) - bestBlockHash := babeService.blockState.BestBlockHash() - runtime, err := babeService.blockState.GetRuntime(bestBlockHash) + epochDescriptor, err := babeService.initiateEpoch(testEpochIndex) require.NoError(t, err) + epochDescriptor.data.threshold = maxThreshold - epochData, err := babeService.initiateEpoch(testEpochIndex) - require.NoError(t, err) - epochData.threshold = maxThreshold - - slot := getSlot(t, runtime, time.Now()) - block := createTestBlockWithSlot(t, babeService, &genesisHeader, [][]byte{}, testEpochIndex, epochData, slot) + slot := Slot{ + start: getSlotStartTime(epochDescriptor.startSlot, babeService.constants.slotDuration), + duration: babeService.constants.slotDuration, + number: epochDescriptor.startSlot, + } + block := createTestBlockWithSlot(t, babeService, &genesisHeader, [][]byte{}, epochDescriptor, slot) db, err := database.NewPebble(t.TempDir(), true) require.NoError(t, err) slotState := state.NewSlotState(db) verifier := newVerifier(babeService.blockState, slotState, testEpochIndex, &verifierInfo{ - authorities: epochData.authorities, - threshold: epochData.threshold, - randomness: epochData.randomness, + authorities: epochDescriptor.data.authorities, + threshold: epochDescriptor.data.threshold, + randomness: epochDescriptor.data.randomness, }, time.Second) err = verifier.verifyAuthorshipRight(&block.Header) @@ -488,7 +524,7 @@ func TestVerifyAuthorshipRight(t *testing.T) { func TestVerifyAuthorshipRight_Equivocation(t *testing.T) { genesis, genesisTrie, genesisHeader := newWestendDevGenesisWithTrieAndHeader(t) - babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, nil) + babeService := createTestService(t, ServiceConfig{}, genesis, genesisTrie, genesisHeader, AuthorOnEverySlotBABEConfig) db, err := database.NewPebble(t.TempDir(), true) require.NoError(t, err) @@ -517,11 +553,11 @@ func TestVerifyAuthorshipRight_Equivocation(t *testing.T) { slot = NewSlot(startTime.Add(6*time.Second), slotDuration, slotNumber+1) } - block := createTestBlockWithSlot(t, babeService, &genesisHeader, [][]byte{}, testEpochIndex, epochData, *slot) + block := createTestBlockWithSlot(t, babeService, &genesisHeader, [][]byte{}, epochData, *slot) block.Header.Hash() // create new block for same slot - block2 := createTestBlockWithSlot(t, babeService, &genesisHeader, [][]byte{}, testEpochIndex, epochData, *slot) + block2 := createTestBlockWithSlot(t, babeService, &genesisHeader, [][]byte{}, epochData, *slot) block2.Header.Hash() err = babeService.blockState.AddBlock(block) @@ -579,8 +615,9 @@ func TestVerifyForkBlocksWithRespectiveEpochData(t *testing.T) { ) stateService := state.NewService(state.Config{ - Path: t.TempDir(), - Telemetry: telemetryMock, + Path: t.TempDir(), + Telemetry: telemetryMock, + GenesisBABEConfig: config.BABEConfigurationTestDefault, }) stateService.UseMemDB() diff --git a/lib/babe/verify_test.go b/lib/babe/verify_test.go index a6e2ee4e80..280770ce93 100644 --- a/lib/babe/verify_test.go +++ b/lib/babe/verify_test.go @@ -135,7 +135,7 @@ func Test_getAuthorityIndex(t *testing.T) { headerSecondary := types.NewEmptyHeader() headerSecondary.Digest = digestSecondary - //BabeSecondaryPlainPreDigest case + // BabeSecondaryPlainPreDigest case babeDigest3 := types.NewBabeDigest() err = babeDigest3.SetValue(types.BabeSecondaryPlainPreDigest{AuthorityIndex: 21, SlotNumber: 100}) assert.NoError(t, err) @@ -215,7 +215,7 @@ func Test_verifier_verifyPrimarySlotWinner(t *testing.T) { ctrl := gomock.NewController(t) mockBlockState := NewMockBlockState(ctrl) - //Generate keys + // Generate keys kp, err := sr25519.GenerateKeypair() assert.NoError(t, err) @@ -297,7 +297,7 @@ func Test_verifier_verifyPreRuntimeDigest(t *testing.T) { ctrl := gomock.NewController(t) mockBlockState := NewMockBlockState(ctrl) - //Generate keys + // Generate keys kp, err := sr25519.GenerateKeypair() assert.NoError(t, err) @@ -362,7 +362,7 @@ func Test_verifier_verifyPreRuntimeDigest(t *testing.T) { secondarySlots: true, } - //BabeSecondaryPlainPreDigest case + // BabeSecondaryPlainPreDigest case secDigest := types.BabeSecondaryPlainPreDigest{AuthorityIndex: 0, SlotNumber: uint64(1)} prd, err := secDigest.ToPreRuntimeDigest() assert.NoError(t, err) @@ -467,7 +467,7 @@ func Test_verifier_verifyAuthorshipRight(t *testing.T) { ctrl := gomock.NewController(t) mockBlockState := NewMockBlockState(ctrl) - //Generate keys + // Generate keys kp, err := sr25519.GenerateKeypair() assert.NoError(t, err) @@ -675,7 +675,7 @@ func Test_verifier_verifyAuthorshipRight(t *testing.T) { } func Test_verifyBlockEquivocation(t *testing.T) { - //t.Parallel() + t.Parallel() kp, err := sr25519.GenerateKeypair() assert.NoError(t, err) @@ -911,8 +911,7 @@ func Test_verifyBlockEquivocation(t *testing.T) { tt := tt t.Run(tname, func(t *testing.T) { - //t.Parallel() - + t.Parallel() verifier := tt.buildVerifier(t) out, err := verifier.verifyBlockEquivocation(tt.header) require.ErrorIs(t, err, tt.wantErr) @@ -1305,40 +1304,23 @@ func TestVerificationManager_getVerifierInfo(t *testing.T) { } func TestVerificationManager_VerifyBlock(t *testing.T) { - //Generate keys + // Generate keys kp, err := sr25519.GenerateKeypair() assert.NoError(t, err) - // Create a VRF output and proof - output, proof, err := kp.VrfSign(makeTranscript(Randomness{}, uint64(1), 1)) - assert.NoError(t, err) - testBlockHeaderEmpty := types.NewEmptyHeader() testBlockHeaderEmpty.Number = 2 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) mockEpochStateVerifyAuthorshipErr := NewMockEpochState(ctrl) - errTestNumberIsFinalised := errors.New("test number is finalised error") - mockBlockStateCheckFinErr.EXPECT().NumberIsFinalised(uint(1)).Return(false, errTestNumberIsFinalised) - - mockBlockStateNotFinal.EXPECT().NumberIsFinalised(uint(1)).Return(false, nil) - - mockBlockStateNotFinal2.EXPECT().NumberIsFinalised(uint(1)).Return(false, nil) - errTestSetFirstSlot := errors.New("test set first slot error") - mockEpochStateSetSlotErr.EXPECT().SetFirstSlot(uint64(1)).Return(errTestSetFirstSlot) - errTestGetEpoch := errors.New("test get epoch error") mockEpochStateGetEpochErr.EXPECT().GetEpochForBlock(testBlockHeaderEmpty). Return(uint64(0), errTestGetEpoch) @@ -1364,16 +1346,6 @@ func TestVerificationManager_VerifyBlock(t *testing.T) { block1Header := types.NewEmptyHeader() block1Header.Number = 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)) - authority := types.NewAuthority(kp.Public(), uint64(1)) info := &verifierInfo{ authorities: []types.AuthorityRaw{*authority.ToRaw(), *authority.ToRaw()}, @@ -1383,9 +1355,6 @@ func TestVerificationManager_VerifyBlock(t *testing.T) { mockSlotState := NewMockSlotState(nil) - vm0 := NewVerificationManager(mockBlockStateCheckFinErr, mockSlotState, mockEpochStateEmpty) - vm1 := NewVerificationManager(mockBlockStateNotFinal, mockSlotState, mockEpochStateEmpty) - vm2 := NewVerificationManager(mockBlockStateNotFinal2, mockSlotState, mockEpochStateSetSlotErr) vm3 := NewVerificationManager(mockBlockStateNotFinal2, mockSlotState, mockEpochStateGetEpochErr) vm4 := NewVerificationManager(mockBlockStateEmpty, mockSlotState, mockEpochStateSkipVerifyErr) vm5 := NewVerificationManager(mockBlockStateEmpty, mockSlotState, mockEpochStateSkipVerifyTrue) @@ -1399,24 +1368,6 @@ func TestVerificationManager_VerifyBlock(t *testing.T) { 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", errTestNumberIsFinalised), - }, - { - name: "get slot from header error", - vm: vm1, - header: block1Header, - expErr: fmt.Errorf("failed to get slot from header of block 1: %w", types.ErrChainHeadMissingDigest), - }, - { - name: "set first slot error", - vm: vm2, - header: block1Header2, - expErr: fmt.Errorf("failed to set current epoch after receiving block 1: %w", errTestSetFirstSlot), - }, { name: "get epoch error", vm: vm3, @@ -1462,7 +1413,7 @@ func TestVerificationManager_VerifyBlock(t *testing.T) { } func TestVerificationManager_SetOnDisabled(t *testing.T) { - //Generate keys + // Generate keys kp, err := sr25519.GenerateKeypair() assert.NoError(t, err) diff --git a/lib/blocktree/blocktree.go b/lib/blocktree/blocktree.go index 0fa4e4d06e..0f31f5820f 100644 --- a/lib/blocktree/blocktree.go +++ b/lib/blocktree/blocktree.go @@ -126,6 +126,10 @@ func (bt *BlockTree) GetHashesAtNumber(number uint) (hashes []common.Hash) { bt.RLock() defer bt.RUnlock() + if bt.root == nil { + return nil + } + if number < bt.root.number { return []common.Hash{} } diff --git a/tests/utils/config/default.go b/tests/utils/config/default.go index 73f1cca33c..bf5b63817b 100644 --- a/tests/utils/config/default.go +++ b/tests/utils/config/default.go @@ -7,6 +7,7 @@ import ( "time" cfg "github.com/ChainSafe/gossamer/config" + "github.com/ChainSafe/gossamer/dot/types" wazero_runtime "github.com/ChainSafe/gossamer/lib/runtime/wazero" ) @@ -68,3 +69,13 @@ func Default() cfg.Config { System: &cfg.SystemConfig{}, } } + +var BABEConfigurationTestDefault = &types.BabeConfiguration{ + SlotDuration: 1000, + EpochLength: 200, + C1: 1, + C2: 4, + GenesisAuthorities: []types.AuthorityRaw{}, + Randomness: [32]byte{}, + SecondarySlots: 0, +}