From 5c3dbfe9de22b917c25f113aab258a812df6d644 Mon Sep 17 00:00:00 2001 From: noot <36753753+noot@users.noreply.github.com> Date: Thu, 24 Jun 2021 15:12:26 -0400 Subject: [PATCH] fix(lib/babe): fix setting first slot of network, fix loading BABE epoch params (#1640) --- dot/core/mocks/verifier.go | 27 ---- dot/digest/digest_test.go | 4 +- dot/network/sync.go | 2 +- dot/node.go | 4 +- dot/node_test.go | 5 +- dot/rpc/modules/author_test.go | 1 - dot/rpc/modules/chain_test.go | 37 ++++- dot/state/base.go | 30 ++++ dot/state/base_test.go | 26 ++++ dot/state/block.go | 42 ++++- dot/state/block_test.go | 101 +++++++++++- dot/state/epoch.go | 128 +++++++++++---- dot/state/epoch_test.go | 66 ++++++-- dot/state/service.go | 5 +- dot/state/service_test.go | 5 +- dot/sync/syncer_test.go | 13 +- dot/types/babe.go | 2 +- go.mod | 2 +- lib/babe/babe.go | 231 ++++++++++++++++------------ lib/babe/babe_test.go | 133 ++++++++++++++++ lib/babe/epoch.go | 72 ++++++--- lib/babe/epoch_test.go | 24 ++- lib/babe/errors.go | 1 + lib/babe/state.go | 10 ++ lib/babe/verify.go | 89 +++-------- lib/grandpa/message_handler_test.go | 15 +- lib/grandpa/message_test.go | 8 +- lib/grandpa/network_test.go | 3 + tests/rpc/rpc_01-system_test.go | 2 +- 29 files changed, 794 insertions(+), 294 deletions(-) delete mode 100644 dot/core/mocks/verifier.go diff --git a/dot/core/mocks/verifier.go b/dot/core/mocks/verifier.go deleted file mode 100644 index 4075b959ef..0000000000 --- a/dot/core/mocks/verifier.go +++ /dev/null @@ -1,27 +0,0 @@ -// Code generated by mockery v2.8.0. DO NOT EDIT. - -package core - -import ( - types "github.com/ChainSafe/gossamer/dot/types" - mock "github.com/stretchr/testify/mock" -) - -// MockVerifier is an autogenerated mock type for the Verifier type -type MockVerifier struct { - mock.Mock -} - -// SetOnDisabled provides a mock function with given fields: authorityIndex, block -func (_m *MockVerifier) SetOnDisabled(authorityIndex uint32, block *types.Header) error { - ret := _m.Called(authorityIndex, block) - - var r0 error - if rf, ok := ret.Get(0).(func(uint32, *types.Header) error); ok { - r0 = rf(authorityIndex, block) - } else { - r0 = ret.Error(0) - } - - return r0 -} diff --git a/dot/digest/digest_test.go b/dot/digest/digest_test.go index 7af5a21206..c0f6839a3f 100644 --- a/dot/digest/digest_test.go +++ b/dot/digest/digest_test.go @@ -52,7 +52,9 @@ func addTestBlocksToStateWithParent(t *testing.T, previousHash common.Hash, dept Header: &types.Header{ ParentHash: previousHash, Number: big.NewInt(int64(i)).Add(previousNum, big.NewInt(int64(i))), - Digest: types.Digest{}, + Digest: types.Digest{ + types.NewBabeSecondaryPlainPreDigest(0, uint64(i)).ToPreRuntimeDigest(), + }, }, Body: &types.Body{}, } diff --git a/dot/network/sync.go b/dot/network/sync.go index 22e1993ab9..855e4d6a25 100644 --- a/dot/network/sync.go +++ b/dot/network/sync.go @@ -578,7 +578,7 @@ func (q *syncQueue) processBlockRequests() { } func (q *syncQueue) trySync(req *syncRequest) { - if q.ctx.Err() != nil { + if q.ctx.Err() != nil || len(q.s.host.peers()) == 0 { return } diff --git a/dot/node.go b/dot/node.go index 016436a866..768999bb40 100644 --- a/dot/node.go +++ b/dot/node.go @@ -51,6 +51,7 @@ type Node struct { Services *services.ServiceRegistry // registry of all node services StopFunc func() // func to call when node stops, currently used for profiling wg sync.WaitGroup + started chan struct{} } // InitNode initialises a new dot node from the provided dot node configuration @@ -323,6 +324,7 @@ func NewNode(cfg *Config, ks *keystore.GlobalKeystore, stopFunc func()) (*Node, Name: cfg.Global.Name, StopFunc: stopFunc, Services: services.NewServiceRegistry(), + started: make(chan struct{}), } for _, srvc := range nodeSrvcs { @@ -429,8 +431,8 @@ func (n *Node) Start() error { }() n.wg.Add(1) + close(n.started) n.wg.Wait() - return nil } diff --git a/dot/node_test.go b/dot/node_test.go index 253051cc2f..00edec4bcb 100644 --- a/dot/node_test.go +++ b/dot/node_test.go @@ -22,7 +22,6 @@ import ( "reflect" "sync" "testing" - "time" "github.com/ChainSafe/gossamer/dot/core" "github.com/ChainSafe/gossamer/dot/state" @@ -188,9 +187,7 @@ func TestStartNode(t *testing.T) { require.NoError(t, err) go func() { - // TODO: need to wait until all services are started so that wg.Add is called, otherwise - // will call wg.Done before the counter is at 1 - time.Sleep(time.Second * 15) + <-node.started node.Stop() }() diff --git a/dot/rpc/modules/author_test.go b/dot/rpc/modules/author_test.go index ef4e1bf193..7755cb405b 100644 --- a/dot/rpc/modules/author_test.go +++ b/dot/rpc/modules/author_test.go @@ -136,7 +136,6 @@ func TestAuthorModule_SubmitExtrinsic_invalid_input(t *testing.T) { ext := Extrinsic{fmt.Sprintf("%x", "1")} res := new(ExtrinsicHashResponse) - err := auth.SubmitExtrinsic(nil, &ext, res) require.EqualError(t, err, "could not byteify non 0x prefixed string") } diff --git a/dot/rpc/modules/chain_test.go b/dot/rpc/modules/chain_test.go index a6d2ad6279..d8344eb242 100644 --- a/dot/rpc/modules/chain_test.go +++ b/dot/rpc/modules/chain_test.go @@ -39,12 +39,17 @@ func TestChainGetHeader_Genesis(t *testing.T) { header, err := state.Block.BestBlockHeader() require.NoError(t, err) + d, err := types.NewBabeSecondaryPlainPreDigest(0, 1).ToPreRuntimeDigest().Encode() + require.NoError(t, err) + expected := &ChainBlockHeaderResponse{ ParentHash: header.ParentHash.String(), Number: common.BytesToHex(header.Number.Bytes()), StateRoot: header.StateRoot.String(), ExtrinsicsRoot: header.ExtrinsicsRoot.String(), - Digest: ChainBlockHeaderDigest{}, + Digest: ChainBlockHeaderDigest{ + Logs: []string{common.BytesToHex(d)}, + }, } hash := state.Block.BestBlockHash() @@ -64,12 +69,17 @@ func TestChainGetHeader_Latest(t *testing.T) { header, err := state.Block.BestBlockHeader() require.NoError(t, err) + d, err := types.NewBabeSecondaryPlainPreDigest(0, 1).ToPreRuntimeDigest().Encode() + require.NoError(t, err) + expected := &ChainBlockHeaderResponse{ ParentHash: header.ParentHash.String(), Number: common.BytesToHex(header.Number.Bytes()), StateRoot: header.StateRoot.String(), ExtrinsicsRoot: header.ExtrinsicsRoot.String(), - Digest: ChainBlockHeaderDigest{}, + Digest: ChainBlockHeaderDigest{ + Logs: []string{common.BytesToHex(d)}, + }, } res := &ChainBlockHeaderResponse{} @@ -101,12 +111,17 @@ func TestChainGetBlock_Genesis(t *testing.T) { header, err := state.Block.BestBlockHeader() require.NoError(t, err) + d, err := types.NewBabeSecondaryPlainPreDigest(0, 1).ToPreRuntimeDigest().Encode() + require.NoError(t, err) + expectedHeader := &ChainBlockHeaderResponse{ ParentHash: header.ParentHash.String(), Number: common.BytesToHex(header.Number.Bytes()), StateRoot: header.StateRoot.String(), ExtrinsicsRoot: header.ExtrinsicsRoot.String(), - Digest: ChainBlockHeaderDigest{}, + Digest: ChainBlockHeaderDigest{ + Logs: []string{common.BytesToHex(d)}, + }, } hash := state.Block.BestBlockHash() @@ -134,12 +149,17 @@ func TestChainGetBlock_Latest(t *testing.T) { header, err := state.Block.BestBlockHeader() require.NoError(t, err) + d, err := types.NewBabeSecondaryPlainPreDigest(0, 1).ToPreRuntimeDigest().Encode() + require.NoError(t, err) + expectedHeader := &ChainBlockHeaderResponse{ ParentHash: header.ParentHash.String(), Number: common.BytesToHex(header.Number.Bytes()), StateRoot: header.StateRoot.String(), ExtrinsicsRoot: header.ExtrinsicsRoot.String(), - Digest: ChainBlockHeaderDigest{}, + Digest: ChainBlockHeaderDigest{ + Logs: []string{common.BytesToHex(d)}, + }, } expected := &ChainBlockResponse{ @@ -267,6 +287,9 @@ func TestChainGetFinalizedHeadByRound(t *testing.T) { header := &types.Header{ Number: big.NewInt(1), + Digest: types.Digest{ + types.NewBabeSecondaryPlainPreDigest(0, 1).ToPreRuntimeDigest(), + }, } err = state.Block.SetHeader(header) require.NoError(t, err) @@ -358,8 +381,10 @@ func loadTestBlocks(gh common.Hash, bs *state.BlockState) error { // Create header & blockData for block 1 header1 := &types.Header{ - Number: big.NewInt(1), - Digest: types.Digest{}, + Number: big.NewInt(1), + Digest: types.Digest{ + types.NewBabeSecondaryPlainPreDigest(0, 1).ToPreRuntimeDigest(), + }, ParentHash: blockHash0, StateRoot: trie.EmptyHash, } diff --git a/dot/state/base.go b/dot/state/base.go index 08401b3c3f..9e0e8884f3 100644 --- a/dot/state/base.go +++ b/dot/state/base.go @@ -155,6 +155,36 @@ func (s *BaseState) loadFirstSlot() (uint64, error) { 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 +} + // storePruningData stores the pruner configuration. func (s *BaseState) storePruningData(mode pruner.Config) error { encMode, err := json.Marshal(mode) diff --git a/dot/state/base_test.go b/dot/state/base_test.go index a6aee30313..957a81323a 100644 --- a/dot/state/base_test.go +++ b/dot/state/base_test.go @@ -112,3 +112,29 @@ func TestStoreAndLoadBestBlockHash(t *testing.T) { require.NoError(t, err) require.Equal(t, hash, res) } + +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.go b/dot/state/block.go index 82b7aeab49..195239f49b 100644 --- a/dot/state/block.go +++ b/dot/state/block.go @@ -42,7 +42,8 @@ type BlockState struct { baseState *BaseState db chaindb.Database sync.RWMutex - genesisHash common.Hash + genesisHash common.Hash + lastFinalised common.Hash // block notifiers imported map[byte]chan<- *types.Block @@ -74,6 +75,11 @@ func NewBlockState(db chaindb.Database, bt *blocktree.BlockTree) (*BlockState, e } bs.genesisHash = genesisBlock.Header.Hash() + bs.lastFinalised, err = bs.GetFinalizedHash(0, 0) + if err != nil { + return nil, fmt.Errorf("failed to get last finalised hash: %w", err) + } + return bs, nil } @@ -372,6 +378,16 @@ func (bs *BlockState) HasFinalizedBlock(round, setID uint64) (bool, error) { return bs.db.Has(finalizedHashKey(round, setID)) } +// NumberIsFinalised checks if a block number is finalised or not +func (bs *BlockState) NumberIsFinalised(num *big.Int) (bool, error) { + header, err := bs.GetFinalizedHeader(0, 0) + if err != nil { + return false, err + } + + return num.Cmp(header.Number) <= 0, nil +} + // GetFinalizedHeader returns the latest finalised block header func (bs *BlockState) GetFinalizedHeader(round, setID uint64) (*types.Header, error) { h, err := bs.GetFinalizedHash(round, setID) @@ -407,6 +423,15 @@ func (bs *BlockState) SetFinalizedHash(hash common.Hash, round, setID uint64) er return fmt.Errorf("cannot finalise unknown block %s", 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.Equal(bs.genesisHash) && !hash.Equal(bs.genesisHash) { + err := bs.setFirstSlotOnFinalisation() + if err != nil { + return err + } + } + go bs.notifyFinalized(hash, round, setID) if round > 0 { err := bs.SetRound(round) @@ -431,9 +456,24 @@ func (bs *BlockState) SetFinalizedHash(hash common.Hash, round, setID uint64) er bs.pruneKeyCh <- header } + bs.lastFinalised = hash return bs.db.Put(finalizedHashKey(round, setID), hash[:]) } +func (bs *BlockState) setFirstSlotOnFinalisation() error { + header, err := bs.GetHeaderByNumber(big.NewInt(1)) + if err != nil { + return err + } + + slot, err := types.GetSlotFromHeader(header) + if err != nil { + return err + } + + return bs.baseState.storeFirstSlot(slot) +} + // SetRound sets the latest finalised GRANDPA round in the db // TODO: this needs to use both setID and round func (bs *BlockState) SetRound(round uint64) error { diff --git a/dot/state/block_test.go b/dot/state/block_test.go index 0f19f5c224..88451da523 100644 --- a/dot/state/block_test.go +++ b/dot/state/block_test.go @@ -273,10 +273,24 @@ func TestFinalizedHash(t *testing.T) { require.NoError(t, err) require.Equal(t, testGenesisHeader.Hash(), h) - testhash := common.Hash{1, 2, 3, 4} + header := &types.Header{ + ParentHash: testGenesisHeader.Hash(), + Number: big.NewInt(1), + Digest: types.Digest{ + types.NewBabeSecondaryPlainPreDigest(0, 1).ToPreRuntimeDigest(), + }, + } + + testhash := header.Hash() err = bs.db.Put(headerKey(testhash), []byte{}) require.NoError(t, err) + err = bs.AddBlock(&types.Block{ + Header: header, + Body: &types.Body{}, + }) + require.NoError(t, err) + err = bs.SetFinalizedHash(testhash, 1, 1) require.NoError(t, err) @@ -520,3 +534,88 @@ func TestAddBlockToBlockTree(t *testing.T) { require.NoError(t, err) require.Equal(t, bs.BestBlockHash(), header.Hash()) } + +func TestNumberIsFinalised(t *testing.T) { + bs := newTestBlockState(t, testGenesisHeader) + fin, err := bs.NumberIsFinalised(big.NewInt(0)) + require.NoError(t, err) + require.True(t, fin) + + fin, err = bs.NumberIsFinalised(big.NewInt(1)) + require.NoError(t, err) + require.False(t, fin) + + header1 := &types.Header{ + Number: big.NewInt(1), + Digest: types.Digest{ + types.NewBabeSecondaryPlainPreDigest(0, 1).ToPreRuntimeDigest(), + }, + ParentHash: testGenesisHeader.Hash(), + } + + header100 := &types.Header{ + Number: big.NewInt(100), + Digest: types.Digest{ + types.NewBabeSecondaryPlainPreDigest(0, 100).ToPreRuntimeDigest(), + }, + ParentHash: testGenesisHeader.Hash(), + } + + err = bs.SetHeader(header1) + require.NoError(t, err) + err = bs.db.Put(headerHashKey(header1.Number.Uint64()), header1.Hash().ToBytes()) + require.NoError(t, err) + + err = bs.SetHeader(header100) + require.NoError(t, err) + err = bs.SetFinalizedHash(header100.Hash(), 0, 0) + require.NoError(t, err) + + fin, err = bs.NumberIsFinalised(big.NewInt(0)) + require.NoError(t, err) + require.True(t, fin) + + fin, err = bs.NumberIsFinalised(big.NewInt(1)) + require.NoError(t, err) + require.True(t, fin) + + fin, err = bs.NumberIsFinalised(big.NewInt(100)) + require.NoError(t, err) + require.True(t, fin) +} + +func TestSetFinalisedHash_setFirstSlotOnFinalisation(t *testing.T) { + bs := newTestBlockState(t, testGenesisHeader) + firstSlot := uint64(42069) + + header1 := &types.Header{ + Number: big.NewInt(1), + Digest: types.Digest{ + types.NewBabeSecondaryPlainPreDigest(0, firstSlot).ToPreRuntimeDigest(), + }, + ParentHash: testGenesisHeader.Hash(), + } + + header100 := &types.Header{ + Number: big.NewInt(100), + Digest: types.Digest{ + types.NewBabeSecondaryPlainPreDigest(0, firstSlot+100).ToPreRuntimeDigest(), + }, + ParentHash: testGenesisHeader.Hash(), + } + + err := bs.SetHeader(header1) + require.NoError(t, err) + err = bs.db.Put(headerHashKey(header1.Number.Uint64()), header1.Hash().ToBytes()) + require.NoError(t, err) + + err = bs.SetHeader(header100) + require.NoError(t, err) + err = bs.SetFinalizedHash(header100.Hash(), 0, 0) + require.NoError(t, err) + require.Equal(t, header100.Hash(), bs.lastFinalised) + + res, err := bs.baseState.loadFirstSlot() + require.NoError(t, err) + require.Equal(t, firstSlot, res) +} diff --git a/dot/state/epoch.go b/dot/state/epoch.go index fc45af6d9a..01dc6f45f3 100644 --- a/dot/state/epoch.go +++ b/dot/state/epoch.go @@ -21,6 +21,8 @@ import ( "encoding/binary" "errors" "fmt" + "math/big" + "time" "github.com/ChainSafe/chaindb" "github.com/ChainSafe/gossamer/dot/types" @@ -28,13 +30,15 @@ import ( ) var ( - epochPrefix = "epoch" - epochLengthKey = []byte("epochlength") - currentEpochKey = []byte("current") - firstSlotKey = []byte("firstslot") - epochDataPrefix = []byte("epochinfo") - configDataPrefix = []byte("configinfo") - skipToKey = []byte("skipto") + epochPrefix = "epoch" + epochLengthKey = []byte("epochlength") + currentEpochKey = []byte("current") + firstSlotKey = []byte("firstslot") + slotDurationKey = []byte("slotduration") + epochDataPrefix = []byte("epochinfo") + configDataPrefix = []byte("configinfo") + latestConfigDataKey = []byte("lcfginfo") + skipToKey = []byte("skipto") ) func epochDataKey(epoch uint64) []byte { @@ -53,8 +57,8 @@ func configDataKey(epoch uint64) []byte { type EpochState struct { db chaindb.Database baseState *BaseState + blockState *BlockState epochLength uint64 // measured in slots - firstSlot uint64 skipToEpoch uint64 } @@ -81,7 +85,6 @@ func NewEpochStateFromGenesis(db chaindb.Database, genesisConfig *types.BabeConf baseState: NewBaseState(db), db: epochDB, epochLength: genesisConfig.EpochLength, - firstSlot: 1, } auths, err := types.BABEAuthorityRawToAuthority(genesisConfig.GenesisAuthorities) @@ -106,8 +109,11 @@ func NewEpochStateFromGenesis(db chaindb.Database, genesisConfig *types.BabeConf return nil, err } - err = storeEpochLength(db, genesisConfig.EpochLength) - if err != nil { + if err = s.baseState.storeEpochLength(genesisConfig.EpochLength); err != nil { + return nil, err + } + + if err = s.baseState.storeSlotDuration(genesisConfig.SlotDuration); err != nil { return nil, err } @@ -115,19 +121,17 @@ func NewEpochStateFromGenesis(db chaindb.Database, genesisConfig *types.BabeConf return nil, err } + s.blockState = &BlockState{ + db: chaindb.NewTable(db, blockPrefix), + } return s, nil } // NewEpochState returns a new EpochState -func NewEpochState(db chaindb.Database) (*EpochState, error) { +func NewEpochState(db chaindb.Database, blockState *BlockState) (*EpochState, error) { baseState := NewBaseState(db) - epochLength, err := loadEpochLength(db) - if err != nil { - return nil, err - } - - firstSlot, err := baseState.loadFirstSlot() + epochLength, err := baseState.loadEpochLength() if err != nil { return nil, err } @@ -139,26 +143,26 @@ func NewEpochState(db chaindb.Database) (*EpochState, error) { return &EpochState{ baseState: baseState, + blockState: blockState, db: chaindb.NewTable(db, epochPrefix), epochLength: epochLength, - firstSlot: firstSlot, skipToEpoch: skipToEpoch, }, nil } -func storeEpochLength(db chaindb.Database, l uint64) error { - buf := make([]byte, 8) - binary.LittleEndian.PutUint64(buf, l) - return db.Put(epochLengthKey, buf) +// GetEpochLength returns the length of an epoch in slots +func (s *EpochState) GetEpochLength() (uint64, error) { + return s.baseState.loadEpochLength() } -func loadEpochLength(db chaindb.Database) (uint64, error) { - data, err := db.Get(epochLengthKey) +// 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 binary.LittleEndian.Uint64(data), nil + return time.ParseDuration(fmt.Sprintf("%dms", d)) } // SetCurrentEpoch sets the current epoch @@ -184,6 +188,11 @@ func (s *EpochState) GetEpochForBlock(header *types.Header) (uint64, error) { return 0, errors.New("header is nil") } + firstSlot, err := s.baseState.loadFirstSlot() + if err != nil { + return 0, err + } + for _, d := range header.Digest { if d.Type() != types.PreRuntimeDigestType { continue @@ -198,7 +207,11 @@ func (s *EpochState) GetEpochForBlock(header *types.Header) (uint64, error) { return 0, fmt.Errorf("failed to decode babe header: %w", err) } - return (digest.SlotNumber() - s.firstSlot) / s.epochLength, nil + if digest.SlotNumber() < firstSlot { + return 0, nil + } + + return (digest.SlotNumber() - firstSlot) / s.epochLength, nil } return 0, errors.New("header does not contain pre-runtime digest") @@ -258,9 +271,20 @@ func (s *EpochState) SetConfigData(epoch uint64, info *types.ConfigData) error { return err } + // this assumes the most recently set config data is the highest on the chain + if err = s.setLatestConfigData(epoch); err != nil { + return err + } + return s.db.Put(configDataKey(epoch), enc) } +func (s *EpochState) setLatestConfigData(epoch uint64) error { + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, epoch) + return s.db.Put(latestConfigDataKey, buf) +} + // GetConfigData returns the BABE config data for a given epoch func (s *EpochState) GetConfigData(epoch uint64) (*types.ConfigData, error) { enc, err := s.db.Get(configDataKey(epoch)) @@ -276,6 +300,17 @@ func (s *EpochState) GetConfigData(epoch uint64) (*types.ConfigData, error) { return info.(*types.ConfigData), nil } +// GetLatestConfigData returns the most recently set ConfigData +func (s *EpochState) GetLatestConfigData() (*types.ConfigData, error) { + b, err := s.db.Get(latestConfigDataKey) + if err != nil { + return nil, err + } + + epoch := binary.LittleEndian.Uint64(b) + return s.GetConfigData(epoch) +} + // HasConfigData returns whether config data exists for a given epoch func (s *EpochState) HasConfigData(epoch uint64) (bool, error) { return s.db.Has(configDataKey(epoch)) @@ -284,12 +319,47 @@ func (s *EpochState) HasConfigData(epoch uint64) (bool, error) { // 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) { - return s.epochLength*epoch + s.firstSlot, nil + firstSlot, err := s.baseState.loadFirstSlot() + if err != nil { + return 0, err + } + + return s.epochLength*epoch + firstSlot, nil +} + +// GetEpochFromTime returns the epoch for a given time +func (s *EpochState) GetEpochFromTime(t time.Time) (uint64, error) { + slotDuration, err := s.GetSlotDuration() + if err != nil { + return 0, err + } + + firstSlot, err := s.baseState.loadFirstSlot() + if err != nil { + return 0, err + } + + slot := uint64(t.UnixNano()) / uint64(slotDuration.Nanoseconds()) + + if slot < firstSlot { + return 0, errors.New("given time is before network start") + } + + return (slot - firstSlot) / s.epochLength, nil } // SetFirstSlot sets the first slot number of the network func (s *EpochState) SetFirstSlot(slot uint64) error { - s.firstSlot = slot + // check if block 1 was finalised already; if it has, don't set first slot again + header, err := s.blockState.GetFinalizedHeader(0, 0) + if err != nil { + return err + } + + if header.Number.Cmp(big.NewInt(1)) > -1 { + return errors.New("first slot has already been set") + } + return s.baseState.storeFirstSlot(slot) } diff --git a/dot/state/epoch_test.go b/dot/state/epoch_test.go index fb4fca3614..1ace4ceb37 100644 --- a/dot/state/epoch_test.go +++ b/dot/state/epoch_test.go @@ -17,7 +17,9 @@ package state import ( + "fmt" "testing" + "time" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/crypto/sr25519" @@ -42,17 +44,6 @@ func newEpochStateFromGenesis(t *testing.T) *EpochState { return s } -func TestLoadStoreEpochLength(t *testing.T) { - db := NewInMemoryDB(t) - length := uint64(2222) - err := storeEpochLength(db, length) - require.NoError(t, err) - - ret, err := loadEpochLength(db) - require.NoError(t, err) - require.Equal(t, length, ret) -} - func TestNewEpochStateFromGenesis(t *testing.T) { _ = newEpochStateFromGenesis(t) } @@ -149,6 +140,10 @@ func TestEpochState_ConfigData(t *testing.T) { ret, err := s.GetConfigData(1) require.NoError(t, err) require.Equal(t, data, ret) + + ret, err = s.GetLatestConfigData() + require.NoError(t, err) + require.Equal(t, data, ret) } func TestEpochState_GetEpochForBlock(t *testing.T) { @@ -178,3 +173,52 @@ func TestEpochState_GetEpochForBlock(t *testing.T) { 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) + + 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, testGenesisHeader) + + 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) +} diff --git a/dot/state/service.go b/dot/state/service.go index fd187d6865..a617df4df6 100644 --- a/dot/state/service.go +++ b/dot/state/service.go @@ -180,7 +180,7 @@ func (s *Service) Start() error { s.Transaction = NewTransactionState() // create epoch state - s.Epoch, err = NewEpochState(db) + s.Epoch, err = NewEpochState(db, s.Block) if err != nil { return fmt.Errorf("failed to create epoch state: %w", err) } @@ -328,7 +328,7 @@ func (s *Service) Import(header *types.Header, t *trie.Trie, firstSlot uint64) e db: chaindb.NewTable(s.db, storagePrefix), } - epoch, err := NewEpochState(s.db) + epoch, err := NewEpochState(s.db, block) if err != nil { return err } @@ -339,7 +339,6 @@ func (s *Service) Import(header *types.Header, t *trie.Trie, firstSlot uint64) e return err } - epoch.firstSlot = firstSlot blockEpoch, err := epoch.GetEpochForBlock(header) if err != nil { return err diff --git a/dot/state/service_test.go b/dot/state/service_test.go index 70d0a0d48d..5278a789b3 100644 --- a/dot/state/service_test.go +++ b/dot/state/service_test.go @@ -231,11 +231,13 @@ func TestService_PruneStorage(t *testing.T) { dbKey []byte } - //var prunedArr []prunedBlock var toFinalize common.Hash for i := 0; i < 3; i++ { block, trieState := generateBlockWithRandomTrie(t, serv, nil, int64(i+1)) + block.Header.Digest = types.Digest{ + types.NewBabeSecondaryPlainPreDigest(0, uint64(i+1)).ToPreRuntimeDigest(), + } err = serv.Storage.blockState.AddBlock(block) require.NoError(t, err) @@ -391,7 +393,6 @@ func TestService_Import(t *testing.T) { require.NoError(t, err) require.Equal(t, header.StateRoot, root) - require.Equal(t, firstSlot, serv.Epoch.firstSlot) skip, err := serv.Epoch.SkipVerify(header) require.NoError(t, err) require.True(t, skip) diff --git a/dot/sync/syncer_test.go b/dot/sync/syncer_test.go index 12611722af..c7a62163a8 100644 --- a/dot/sync/syncer_test.go +++ b/dot/sync/syncer_test.go @@ -214,13 +214,19 @@ func TestSyncer_ExecuteBlock(t *testing.T) { func TestSyncer_HandleJustification(t *testing.T) { syncer := NewTestSyncer(t, false) + d := types.NewBabeSecondaryPlainPreDigest(0, 1).ToPreRuntimeDigest() header := &types.Header{ - Number: big.NewInt(1), + ParentHash: syncer.blockState.(*state.BlockState).GenesisHash(), + Number: big.NewInt(1), + Digest: types.Digest{d}, } just := []byte("testjustification") - err := syncer.blockState.SetHeader(header) + err := syncer.blockState.AddBlock(&types.Block{ + Header: header, + Body: &types.Body{}, + }) require.NoError(t, err) syncer.handleJustification(header, just) @@ -236,6 +242,9 @@ func TestSyncer_ProcessJustification(t *testing.T) { parent, err := syncer.blockState.(*state.BlockState).BestBlockHeader() require.NoError(t, err) block := BuildBlock(t, syncer.runtime, parent, nil) + block.Header.Digest = types.Digest{ + types.NewBabeSecondaryPlainPreDigest(0, 1).ToPreRuntimeDigest(), + } err = syncer.blockState.(*state.BlockState).AddBlock(block) require.NoError(t, err) diff --git a/dot/types/babe.go b/dot/types/babe.go index 01cb3713b6..a3eaafc674 100644 --- a/dot/types/babe.go +++ b/dot/types/babe.go @@ -95,7 +95,7 @@ func (d *EpochDataRaw) ToEpochData() (*EpochData, error) { type ConfigData struct { C1 uint64 C2 uint64 - SecondarySlots byte + SecondarySlots byte // TODO: this is unused, will need to update BABE verifier to use this } // GetSlotFromHeader returns the BABE slot from the given header diff --git a/go.mod b/go.mod index b85358f59b..01a0602622 100644 --- a/go.mod +++ b/go.mod @@ -58,4 +58,4 @@ require ( google.golang.org/protobuf v1.26.0-rc.1 ) -go 1.15 +go 1.16 diff --git a/lib/babe/babe.go b/lib/babe/babe.go index d16c45f057..b1d84e299c 100644 --- a/lib/babe/babe.go +++ b/lib/babe/babe.go @@ -21,7 +21,6 @@ import ( "context" "errors" "fmt" - "math/big" "os" "sync" "time" @@ -33,7 +32,10 @@ import ( ethmetrics "github.com/ethereum/go-ethereum/metrics" ) -var logger log.Logger +var ( + logger log.Logger + initialWaitTime time.Duration +) // Service contains the VRF keys for the validator, as well as BABE configuation data type Service struct { @@ -61,7 +63,6 @@ type Service struct { slotDuration time.Duration epochData *epochData slotToProof map[uint64]*VrfOutputAndProof // for slots where we are a producer, store the vrf output (bytes 0-32) + proof (bytes 32-96) - isDisabled bool // State variables sync.RWMutex @@ -133,17 +134,20 @@ func NewService(cfg *ServiceConfig) (*Service, error) { blockImportHandler: cfg.BlockImportHandler, } - genCfg, err := babeService.rt.BabeConfiguration() + epoch, err := cfg.EpochState.GetCurrentEpoch() if err != nil { return nil, err } - err = babeService.setEpochData(cfg, genCfg) + err = babeService.setupParameters(cfg) if err != nil { return nil, err } + initialWaitTime = babeService.slotDuration * 5 + logger.Debug("created service", + "epoch", epoch, "block producer", cfg.Authority, "slot duration", babeService.slotDuration, "epoch length (slots)", babeService.epochLength, @@ -155,65 +159,86 @@ func NewService(cfg *ServiceConfig) (*Service, error) { return babeService, nil } -func (b *Service) setEpochData(cfg *ServiceConfig, genCfg *types.BabeConfiguration) (err error) { - b.epochData = &epochData{ - randomness: genCfg.Randomness, +func (b *Service) setupParameters(cfg *ServiceConfig) error { + var err error + b.epochData = &epochData{} + + epochData, err := b.epochState.GetLatestEpochData() + if err != nil { + return err + } + + b.epochData.randomness = epochData.Randomness + + configData, err := b.epochState.GetLatestConfigData() + if err != nil { + return err } // if slot duration is set via the config file, overwrite the runtime value - if cfg.SlotDuration > 0 { + switch { + case cfg.SlotDuration > 0 && cfg.IsDev: // TODO: remove this, needs to be set via runtime b.slotDuration, err = time.ParseDuration(fmt.Sprintf("%dms", cfg.SlotDuration)) - } else { - b.slotDuration, err = time.ParseDuration(fmt.Sprintf("%dms", genCfg.SlotDuration)) + case cfg.SlotDuration > 0 && !cfg.IsDev: + err = errors.New("slot duration modified in config for non-dev chain") + default: + b.slotDuration, err = b.epochState.GetSlotDuration() } if err != nil { return err } - if cfg.AuthData == nil { - b.epochData.authorities, err = types.BABEAuthorityRawToAuthority(genCfg.GenesisAuthorities) - if err != nil { - return err - } - } else { - b.epochData.authorities = cfg.AuthData + switch { + case cfg.EpochLength != 0 && cfg.IsDev: // TODO: remove this, needs to be set via runtime + b.epochLength = cfg.EpochLength + case cfg.EpochLength > 0 && !cfg.IsDev: + err = errors.New("epoch length modified in config for non-dev chain") + default: + b.epochLength, err = b.epochState.GetEpochLength() + } + if err != nil { + return err } - if cfg.Authority { - b.epochData.authorityIndex, err = b.getAuthorityIndex(b.epochData.authorities) - if err != nil { - return err - } + switch { + case cfg.AuthData != nil && cfg.IsDev: // TODO: remove this, needs to be set via runtime + b.epochData.authorities = cfg.AuthData + case cfg.AuthData != nil && !cfg.IsDev: + return errors.New("authority data modified in config for non-dev chain") + default: + b.epochData.authorities = epochData.Authorities } - if cfg.ThresholdDenominator == 0 { - b.epochData.threshold, err = CalculateThreshold(genCfg.C1, genCfg.C2, len(b.epochData.authorities)) - } else { + switch { + case cfg.ThresholdDenominator != 0 && cfg.IsDev: // TODO: remove this, needs to be set via runtime b.epochData.threshold, err = CalculateThreshold(cfg.ThresholdNumerator, cfg.ThresholdDenominator, len(b.epochData.authorities)) + case cfg.ThresholdDenominator != 0 && !cfg.IsDev: + err = errors.New("threshold modified in config for non-dev chain") + default: + b.epochData.threshold, err = CalculateThreshold(configData.C1, configData.C2, len(b.epochData.authorities)) } - if err != nil { return err } - if cfg.EpochLength > 0 { - b.epochLength = cfg.EpochLength - } else { - b.epochLength = genCfg.EpochLength + if !cfg.Authority { + return nil } - return nil + b.epochData.authorityIndex, err = b.getAuthorityIndex(b.epochData.authorities) + return err } // Start starts BABE block authoring func (b *Service) Start() error { - epoch, err := b.epochState.GetCurrentEpoch() - if err != nil { - logger.Error("failed to get current epoch", "error", err) - return err + if !b.authority { + return nil } - go b.initiate(epoch) + // wait a bit to check if we need to sync before initiating + <-time.NewTimer(initialWaitTime).C + + go b.initiate() return nil } @@ -250,15 +275,8 @@ func (b *Service) Resume() error { } b.pause = make(chan struct{}) - - epoch, err := b.epochState.GetCurrentEpoch() - if err != nil { - logger.Error("failed to get current epoch", "error", err) - return err - } - - go b.initiate(epoch) - logger.Info("service resumed", "epoch", epoch) + go b.initiate() + logger.Debug("service resumed") return nil } @@ -274,6 +292,13 @@ func (b *Service) IsPaused() bool { // Stop stops the service. If stop is called, it cannot be resumed. func (b *Service) Stop() error { + if !b.authority { + return nil + } + + b.Lock() + defer b.Unlock() + if b.ctx.Err() != nil { return errors.New("service already stopped") } @@ -290,14 +315,6 @@ func (b *Service) SetRuntime(rt runtime.Instance) { b.rt = rt } -// SetOnDisabled sets the block producer with the given index as disabled -// If this is our node, we stop producing blocks -func (b *Service) SetOnDisabled(authorityIndex uint32) { // TODO: remove this - if authorityIndex == b.epochData.authorityIndex { - b.isDisabled = true - } -} - // Authorities returns the current BABE authorities func (b *Service) Authorities() []*types.Authority { return b.epochData.authorities @@ -328,7 +345,7 @@ func (b *Service) getSlotDuration() time.Duration { return b.slotDuration } -func (b *Service) initiate(epoch uint64) { +func (b *Service) initiate() { if b.blockState == nil { logger.Error("block authoring", "error", "blockState is nil") return @@ -339,68 +356,45 @@ func (b *Service) initiate(epoch uint64) { return } - err := b.invokeBlockAuthoring(epoch) + err := b.invokeBlockAuthoring() if err != nil { logger.Crit("block authoring error", "error", err) } } -func (b *Service) invokeBlockAuthoring(epoch uint64) error { - for { - // get start slot for current epoch - epochStart, err := b.epochState.GetStartSlotForEpoch(epoch) - if err != nil { - logger.Error("failed to get start slot for current epoch", "epoch", epoch, "error", err) - return err - } +func (b *Service) invokeBlockAuthoring() error { + epoch, err := b.epochState.GetCurrentEpoch() + if err != nil { + logger.Error("failed to get current epoch", "error", err) + return err + } - head, err := b.blockState.BestBlockHeader() + for { + err := b.initiateEpoch(epoch) if err != nil { - logger.Error("failed to get best block header", "error", err) + logger.Error("failed to initiate epoch", "epoch", epoch, "error", err) return err } - // if we're at genesis, set the first slot number for the network - if head.Number.Cmp(big.NewInt(0)) == 0 { - epochStart = getCurrentSlot(b.slotDuration) - err = b.epochState.SetFirstSlot(epochStart) - if err != nil { - logger.Error("failed to set first slot number", "error", err) - return err - } - } - - logger.Info("initiating epoch", "number", epoch, "first slot of epoch", epochStart) - err = b.initiateEpoch(epoch) + epochStartSlot, err := b.waitForEpochStart(epoch) if err != nil { - logger.Error("failed to initiate epoch", "epoch", epoch, "error", err) + logger.Error("failed to wait for epoch start", "epoch", epoch, "error", err) return err } - epochStartTime := getSlotStartTime(epochStart, b.slotDuration) - logger.Debug("checking if epoch started", "epoch start", epochStartTime, "now", time.Now()) - - // check if it's time to start the epoch yet. if not, wait until it is - if time.Since(epochStartTime) < 0 { - logger.Debug("waiting for epoch to start") - select { - case <-time.After(time.Until(epochStartTime)): - case <-b.ctx.Done(): - return nil - case <-b.pause: - return nil - } - } - // calculate current slot startSlot := getCurrentSlot(b.slotDuration) - intoEpoch := startSlot - epochStart + intoEpoch := startSlot - epochStartSlot // if the calculated amount of slots "into the epoch" is greater than the epoch length, // we've been offline for more than an epoch, and need to sync. pause BABE for now, syncer will // resume it when ready if b.epochLength <= intoEpoch && !b.dev { - logger.Debug("pausing BABE, need to sync", "slots into epoch", intoEpoch, "startSlot", startSlot, "epochStart", epochStart) + logger.Debug("pausing BABE, need to sync", + "slots into epoch", + intoEpoch, "startSlot", + startSlot, "epochStart", epochStartSlot, + ) return b.Pause() } @@ -422,14 +416,14 @@ func (b *Service) invokeBlockAuthoring(epoch uint64) error { case <-b.pause: return nil case <-slotDone[i]: - if !b.authority { - continue - } - slotNum := startSlot + uint64(i) err = b.handleSlot(slotNum) if err == ErrNotAuthorized { - logger.Debug("not authorized to produce a block in this slot", "slot", slotNum, "slots into epoch", i) + logger.Debug("not authorized to produce a block in this slot", + "epoch", epoch, + "slot", slotNum, + "slots into epoch", slotNum-epochStartSlot, + ) continue } else if err != nil { logger.Warn("failed to handle slot", "slot", slotNum, "error", err) @@ -450,6 +444,41 @@ func (b *Service) invokeBlockAuthoring(epoch uint64) error { } } +func (b *Service) waitForEpochStart(epoch uint64) (uint64, error) { + // get start slot for current epoch + epochStart, err := b.epochState.GetStartSlotForEpoch(epoch) + if err != nil { + logger.Error("failed to get start slot for current epoch", "epoch", epoch, "error", err) + return 0, err + } + + epochStartTime := getSlotStartTime(epochStart, b.slotDuration) + logger.Debug("checking if epoch started", "epoch start", epochStartTime, "now", time.Now()) + + // check if it's time to start the epoch yet. if not, wait until it is + if time.Since(epochStartTime) < 0 { + logger.Debug("waiting for epoch to start") + err = func() error { + timer := time.NewTimer(time.Until(epochStartTime)) + defer timer.Stop() + select { + case <-timer.C: + return nil + case <-b.ctx.Done(): + return errors.New("context cancelled") + case <-b.pause: + return errors.New("service paused") + } + }() + + if err != nil { + return 0, err + } + } + + return epochStart, nil +} + func (b *Service) handleSlot(slotNum uint64) error { if _, has := b.slotToProof[slotNum]; !has { return ErrNotAuthorized diff --git a/lib/babe/babe_test.go b/lib/babe/babe_test.go index 5a9733f483..ae58d2c65e 100644 --- a/lib/babe/babe_test.go +++ b/lib/babe/babe_test.go @@ -17,6 +17,7 @@ package babe import ( + "fmt" "io/ioutil" "math/big" "os" @@ -132,6 +133,10 @@ func createTestService(t *testing.T, cfg *ServiceConfig) *Service { err = dbSrv.Start() require.NoError(t, err) + t.Cleanup(func() { + _ = dbSrv.Stop() + }) + cfg.BlockState = dbSrv.Block cfg.StorageState = dbSrv.Storage cfg.EpochState = dbSrv.Epoch @@ -146,6 +151,7 @@ func createTestService(t *testing.T, cfg *ServiceConfig) *Service { cfg.Runtime = rt } + cfg.IsDev = true cfg.LogLvl = defaultTestLogLvl babeService, err := NewService(cfg) require.NoError(t, err) @@ -171,6 +177,133 @@ func TestMain(m *testing.M) { os.Exit(code) } +func newTestServiceSetupParameters(t *testing.T) (*Service, *state.EpochState, *types.BabeConfiguration) { + testDatadirPath, err := ioutil.TempDir("/tmp", "test-datadir-*") + require.NoError(t, err) + + config := state.Config{ + Path: testDatadirPath, + LogLevel: log.LvlInfo, + } + dbSrv := state.NewService(config) + dbSrv.UseMemDB() + + gen, genTrie, genHeader := newTestGenesisWithTrieAndHeader(t) + err = dbSrv.Initialise(gen, genHeader, genTrie) + require.NoError(t, err) + + err = dbSrv.Start() + require.NoError(t, err) + + t.Cleanup(func() { + _ = dbSrv.Stop() + }) + + rtCfg := &wasmer.Config{} + rtCfg.Storage, err = rtstorage.NewTrieState(genTrie) + require.NoError(t, err) + rt, err := wasmer.NewRuntimeFromGenesis(gen, rtCfg) //nolint + require.NoError(t, err) + + genCfg, err := rt.BabeConfiguration() + require.NoError(t, err) + + s := &Service{ + epochState: dbSrv.Epoch, + } + + return s, dbSrv.Epoch, genCfg +} + +func TestService_setupParameters_genesis(t *testing.T) { + s, _, genCfg := newTestServiceSetupParameters(t) + + cfg := &ServiceConfig{} + err := s.setupParameters(cfg) + require.NoError(t, err) + slotDuration, err := time.ParseDuration(fmt.Sprintf("%dms", genCfg.SlotDuration)) + require.NoError(t, err) + auths, err := types.BABEAuthorityRawToAuthority(genCfg.GenesisAuthorities) + require.NoError(t, err) + threshold, err := CalculateThreshold(genCfg.C1, genCfg.C2, len(auths)) + require.NoError(t, err) + + require.Equal(t, slotDuration, s.slotDuration) + require.Equal(t, genCfg.EpochLength, s.epochLength) + require.Equal(t, auths, s.epochData.authorities) + require.Equal(t, threshold, s.epochData.threshold) + require.Equal(t, genCfg.Randomness, s.epochData.randomness) +} + +func TestService_setupParameters_epochData(t *testing.T) { + s, epochState, genCfg := newTestServiceSetupParameters(t) + + err := epochState.SetCurrentEpoch(1) + require.NoError(t, err) + + auths, err := types.BABEAuthorityRawToAuthority(genCfg.GenesisAuthorities) + require.NoError(t, err) + + data := &types.EpochData{ + Authorities: auths[:3], + Randomness: [types.RandomnessLength]byte{99, 88, 77}, + } + err = epochState.SetEpochData(1, data) + require.NoError(t, err) + + cfg := &ServiceConfig{} + err = s.setupParameters(cfg) + require.NoError(t, err) + slotDuration, err := time.ParseDuration(fmt.Sprintf("%dms", genCfg.SlotDuration)) + require.NoError(t, err) + threshold, err := CalculateThreshold(genCfg.C1, genCfg.C2, len(data.Authorities)) + require.NoError(t, err) + + require.Equal(t, slotDuration, s.slotDuration) + require.Equal(t, genCfg.EpochLength, s.epochLength) + require.Equal(t, data.Authorities, s.epochData.authorities) + require.Equal(t, data.Randomness, s.epochData.randomness) + require.Equal(t, threshold, s.epochData.threshold) +} + +func TestService_setupParameters_configData(t *testing.T) { + s, epochState, genCfg := newTestServiceSetupParameters(t) + + err := epochState.SetCurrentEpoch(7) + require.NoError(t, err) + + auths, err := types.BABEAuthorityRawToAuthority(genCfg.GenesisAuthorities) + require.NoError(t, err) + + data := &types.EpochData{ + Authorities: auths[:3], + Randomness: [types.RandomnessLength]byte{99, 88, 77}, + } + err = epochState.SetEpochData(7, data) + require.NoError(t, err) + + cfgData := &types.ConfigData{ + C1: 1, + C2: 7, + } + err = epochState.SetConfigData(1, cfgData) // set config data for a previous epoch, ensure latest config data is used + require.NoError(t, err) + + cfg := &ServiceConfig{} + err = s.setupParameters(cfg) + require.NoError(t, err) + slotDuration, err := time.ParseDuration(fmt.Sprintf("%dms", genCfg.SlotDuration)) + require.NoError(t, err) + threshold, err := CalculateThreshold(cfgData.C1, cfgData.C2, len(data.Authorities)) + require.NoError(t, err) + + require.Equal(t, slotDuration, s.slotDuration) + require.Equal(t, genCfg.EpochLength, s.epochLength) + require.Equal(t, data.Authorities, s.epochData.authorities) + require.Equal(t, data.Randomness, s.epochData.randomness) + require.Equal(t, threshold, s.epochData.threshold) +} + func TestService_RunEpochLengthConfig(t *testing.T) { cfg := &ServiceConfig{ EpochLength: 5, diff --git a/lib/babe/epoch.go b/lib/babe/epoch.go index 5589271a38..12605390e0 100644 --- a/lib/babe/epoch.go +++ b/lib/babe/epoch.go @@ -17,9 +17,8 @@ package babe import ( + "errors" "fmt" - - "github.com/ChainSafe/gossamer/dot/types" ) // initiateEpoch sets the epochData for the given epoch, runs the lottery for the slots in the epoch, @@ -30,6 +29,8 @@ func (b *Service) initiateEpoch(epoch uint64) error { err error ) + logger.Debug("initiating epoch", "epoch", epoch) + if epoch == 0 { startSlot, err = b.epochState.GetStartSlotForEpoch(epoch) if err != nil { @@ -41,24 +42,18 @@ func (b *Service) initiateEpoch(epoch uint64) error { return err } - var data *types.EpochData if !has { - data = &types.EpochData{ - Randomness: b.epochData.randomness, - Authorities: b.epochData.authorities, - } - - err = b.epochState.SetEpochData(epoch, data) - } else { - data, err = b.epochState.GetEpochData(epoch) + logger.Crit("no epoch data for next BABE epoch", "epoch", epoch) + return errNoEpochData } + data, err := b.epochState.GetEpochData(epoch) if err != nil { return err } idx, err := b.getAuthorityIndex(data.Authorities) - if err != nil && err != ErrNotAuthority { + if err != nil && !errors.Is(err, ErrNotAuthority) { // TODO: this should be checked in the upper function return err } @@ -97,20 +92,38 @@ func (b *Service) initiateEpoch(epoch uint64) error { if err != nil { return err } - } else if b.blockState.BestBlockHash() == b.blockState.GenesisHash() { - // we are at genesis, set first slot using current time - startSlot = getCurrentSlot(b.slotDuration) - err = b.epochState.SetFirstSlot(startSlot) + } + + // 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 b.blockState.BestBlockHash() == b.blockState.GenesisHash() { + startSlot, err = b.getFirstSlot(epoch) if err != nil { return err } - } - if !b.authority { - return nil + logger.Debug("estimated first slot based on building block 1", "slot", startSlot) + for i := startSlot; i < startSlot+b.epochLength; i++ { + proof, err := b.runLottery(i, epoch) //nolint + if err != nil { + return fmt.Errorf("error running slot lottery at slot %d: error %w", i, err) + } + + if proof != nil { + startSlot = i + break + } + } + + // we are at genesis, set first slot by checking at which slot we will be able to produce block 1 + err = b.epochState.SetFirstSlot(startSlot) + if err != nil { + return err + } } - logger.Debug("initiating epoch", "epoch", epoch, "start slot", startSlot) + logger.Info("initiating epoch", "epoch", epoch, "start slot", startSlot) for i := startSlot; i < startSlot+b.epochLength; i++ { if epoch > 0 { @@ -119,7 +132,7 @@ func (b *Service) initiateEpoch(epoch uint64) error { proof, err := b.runLottery(i, epoch) if err != nil { - return fmt.Errorf("error running slot lottery at slot %d: error %s", i, err) + return fmt.Errorf("error running slot lottery at slot %d: error %w", i, err) } if proof != nil { @@ -131,6 +144,23 @@ func (b *Service) initiateEpoch(epoch uint64) error { return nil } +func (b *Service) getFirstSlot(epoch uint64) (uint64, error) { + startSlot := getCurrentSlot(b.slotDuration) + for i := startSlot; i < startSlot+b.epochLength; i++ { + proof, err := b.runLottery(i, epoch) + if err != nil { + return 0, fmt.Errorf("error running slot lottery at slot %d: error %w", i, err) + } + + if proof != nil { + startSlot = i + break + } + } + + return startSlot, nil +} + // incrementEpoch increments the current epoch stored in the db and returns the new epoch number func (b *Service) incrementEpoch() (uint64, error) { epoch, err := b.epochState.GetCurrentEpoch() diff --git a/lib/babe/epoch_test.go b/lib/babe/epoch_test.go index 880aea8b1f..a0ba5e091b 100644 --- a/lib/babe/epoch_test.go +++ b/lib/babe/epoch_test.go @@ -29,7 +29,7 @@ import ( func TestInitiateEpoch_Epoch0(t *testing.T) { bs := createTestService(t, nil) - bs.epochLength = 10 + bs.epochLength = 20 startSlot := uint64(1000) err := bs.epochState.SetFirstSlot(startSlot) @@ -37,6 +37,9 @@ func TestInitiateEpoch_Epoch0(t *testing.T) { err = bs.initiateEpoch(0) require.NoError(t, err) + startSlot, err = bs.epochState.GetStartSlotForEpoch(0) + require.NoError(t, err) + count := 0 for i := startSlot; i < startSlot+bs.epochLength; i++ { _, has := bs.slotToProof[i] @@ -63,6 +66,13 @@ func TestInitiateEpoch_Epoch1(t *testing.T) { Key: bs.keypair.Public().(*sr25519.PublicKey), Weight: 1, } + + data, err := bs.epochState.GetEpochData(0) + require.NoError(t, err) + data.Authorities = []*types.Authority{auth} + err = bs.epochState.SetEpochData(1, data) + require.NoError(t, err) + err = bs.initiateEpoch(1) require.NoError(t, err) @@ -72,9 +82,19 @@ func TestInitiateEpoch_Epoch1(t *testing.T) { authorityIndex: 0, threshold: threshold, } - require.Equal(t, expected, bs.epochData) + require.Equal(t, expected.randomness, bs.epochData.randomness) + require.Equal(t, expected.authorityIndex, bs.epochData.authorityIndex) + require.Equal(t, expected.threshold, bs.epochData.threshold) require.GreaterOrEqual(t, len(bs.slotToProof), 1) + for i, auth := range bs.epochData.authorities { + expAuth, err := expected.authorities[i].Encode() //nolint + require.NoError(t, err) + res, err := auth.Encode() + require.NoError(t, err) + require.Equal(t, expAuth, res) + } + // for epoch 2, set EpochData but not ConfigData edata := &types.EpochData{ Authorities: bs.epochData.authorities, diff --git a/lib/babe/errors.go b/lib/babe/errors.go index 16f513d839..d8f4b41d94 100644 --- a/lib/babe/errors.go +++ b/lib/babe/errors.go @@ -60,6 +60,7 @@ var ( errNilEpochState = errors.New("cannot have nil EpochState") errNilRuntime = errors.New("runtime is nil") errInvalidResult = errors.New("invalid error value") + errNoEpochData = errors.New("no epoch data found for upcoming epoch") ) // A DispatchOutcomeError is outcome of dispatching the extrinsic diff --git a/lib/babe/state.go b/lib/babe/state.go index c86705f978..417731af82 100644 --- a/lib/babe/state.go +++ b/lib/babe/state.go @@ -43,6 +43,7 @@ type BlockState interface { GetSlotForBlock(common.Hash) (uint64, error) GetFinalizedHeader(uint64, uint64) (*types.Header, error) IsDescendantOf(parent, child common.Hash) (bool, error) + NumberIsFinalised(num *big.Int) (bool, error) } // StorageState interface for storage state methods @@ -60,6 +61,8 @@ type TransactionState interface { // EpochState is the interface for epoch methods type EpochState interface { + GetEpochLength() (uint64, error) + GetSlotDuration() (time.Duration, error) SetCurrentEpoch(epoch uint64) error GetCurrentEpoch() (uint64, error) SetEpochData(uint64, *types.EpochData) error @@ -67,11 +70,18 @@ type EpochState interface { HasEpochData(epoch uint64) (bool, error) GetConfigData(epoch uint64) (*types.ConfigData, error) HasConfigData(epoch uint64) (bool, error) + GetLatestConfigData() (*types.ConfigData, error) GetStartSlotForEpoch(epoch uint64) (uint64, error) GetEpochForBlock(header *types.Header) (uint64, error) SetFirstSlot(slot uint64) error GetLatestEpochData() (*types.EpochData, error) SkipVerify(*types.Header) (bool, error) + GetEpochFromTime(time.Time) (uint64, error) +} + +// DigestHandler is the interface for the consensus digest handler +type DigestHandler interface { + HandleDigests(*types.Header) } // BlockImportHandler is the interface for the handler of new blocks diff --git a/lib/babe/verify.go b/lib/babe/verify.go index 080079626a..7431a042df 100644 --- a/lib/babe/verify.go +++ b/lib/babe/verify.go @@ -141,44 +141,43 @@ func (v *VerificationManager) SetOnDisabled(index uint32, header *types.Header) // VerifyBlock verifies that the block producer for the given block was authorized to produce it. // It returns an error if the block is invalid. func (v *VerificationManager) VerifyBlock(header *types.Header) error { - epoch, err := v.epochState.GetEpochForBlock(header) - if err != nil { - return fmt.Errorf("failed to get epoch for block header: %w", err) - } - var ( info *verifierInfo has bool ) - v.lock.Lock() + // special case for block 1 - the network doesn't necessarily start in epoch 1. + // if this happens, the database will be missing info for epochs before the first block. + if header.Number.Cmp(big.NewInt(1)) == 0 { + block1IsFinal, err := v.blockState.NumberIsFinalised(big.NewInt(1)) + if err != nil { + return fmt.Errorf("failed to check if block 1 is finalised: %w", err) + } - if info, has = v.epochInfo[epoch]; !has { - // special case for block 1 - the network doesn't necessarily start in epoch 1. - // if this happens, the database will be missing info for epochs before the first block. - if header.Number.Cmp(big.NewInt(1)) == 0 { - epoch = 0 - - // set network starting slot - // TODO: first slot should be confirmed when block with number=1 is marked final - var firstSlot uint64 - firstSlot, err = types.GetSlotFromHeader(header) + if !block1IsFinal { + firstSlot, err := types.GetSlotFromHeader(header) if err != nil { - v.lock.Unlock() return fmt.Errorf("failed to get slot from block 1: %w", err) } + logger.Debug("syncing block 1, setting first slot", "slot", firstSlot) + err = v.epochState.SetFirstSlot(firstSlot) if err != nil { - v.lock.Unlock() return fmt.Errorf("failed to set current epoch after receiving block 1: %w", err) } - - info, err = v.getVerifierInfo(0) - } else { - info, err = v.getVerifierInfo(epoch) } + } + + epoch, err := v.epochState.GetEpochForBlock(header) + if err != nil { + return fmt.Errorf("failed to get epoch for block header: %w", err) + } + + v.lock.Lock() + if info, has = v.epochInfo[epoch]; !has { + info, err = v.getVerifierInfo(epoch) if err != nil { v.lock.Unlock() // SkipVerify is set to true only in the case where we have imported a state at a given height, @@ -200,16 +199,6 @@ func (v *VerificationManager) VerifyBlock(header *types.Header) error { v.lock.Unlock() - // TODO: fix and re-add this, seems like we are disabling authorities that aren't actually disabled - // isDisabled, err := v.isDisabled(epoch, header) - // if err != nil { - // return fmt.Errorf("failed to check if authority is disabled: %w", err) - // } - - // if isDisabled { - // return ErrAuthorityDisabled - // } - verifier, err := newVerifier(v.blockState, epoch, info) if err != nil { return fmt.Errorf("failed to create new BABE verifier: %w", err) @@ -218,42 +207,6 @@ func (v *VerificationManager) VerifyBlock(header *types.Header) error { return verifier.verifyAuthorshipRight(header) } -func (v *VerificationManager) isDisabled(epoch uint64, header *types.Header) (bool, error) { //nolint - v.lock.RLock() - defer v.lock.RUnlock() - - // check if any authorities have been disabled this epoch - if _, has := v.onDisabled[epoch]; !has { - return false, nil - } - - // if authorities have been disabled, check which ones - idx, err := getAuthorityIndex(header) - if err != nil { - return false, err - } - - if _, has := v.onDisabled[epoch][idx]; !has { - return false, nil - } - - // this authority has been disabled on some branch, check if we are on that branch - producerInfos := v.onDisabled[epoch][idx] - for _, info := range producerInfos { - isDescendant, err := v.blockState.IsDescendantOf(info.blockHash, header.ParentHash) - if err != nil { - return false, err - } - - if isDescendant && header.Number.Cmp(info.blockNumber) > 0 { - // this authority has been disabled on this branch - return true, nil - } - } - - return false, nil -} - func (v *VerificationManager) getVerifierInfo(epoch uint64) (*verifierInfo, error) { epochData, err := v.epochState.GetEpochData(epoch) if err != nil { diff --git a/lib/grandpa/message_handler_test.go b/lib/grandpa/message_handler_test.go index 077c5fc1fb..ef385b4908 100644 --- a/lib/grandpa/message_handler_test.go +++ b/lib/grandpa/message_handler_test.go @@ -34,6 +34,14 @@ import ( var testHeader = &types.Header{ ParentHash: testGenesisHeader.Hash(), Number: big.NewInt(1), + Digest: types.Digest{ + types.NewBabeSecondaryPlainPreDigest(0, 1).ToPreRuntimeDigest(), + }, +} + +var testBlock = &types.Block{ + Header: testHeader, + Body: &types.Body{}, } var testHash = testHeader.Hash() @@ -198,6 +206,9 @@ func TestMessageHandler_NeighbourMessage(t *testing.T) { Header: &types.Header{ Number: big.NewInt(1), ParentHash: st.Block.GenesisHash(), + Digest: types.Digest{ + types.NewBabeSecondaryPlainPreDigest(0, 1).ToPreRuntimeDigest(), + }, }, Body: &types.Body{0}, } @@ -239,7 +250,7 @@ func TestMessageHandler_CommitMessage_NoCatchUpRequest_ValidSig(t *testing.T) { fm := gs.newCommitMessage(gs.head, round) fm.Vote = NewVote(testHash, uint32(round)) - err := st.Block.SetHeader(testHeader) + err := st.Block.AddBlock(testBlock) require.NoError(t, err) h := NewMessageHandler(gs, st.Block) @@ -328,7 +339,7 @@ func TestMessageHandler_CatchUpRequest_WithResponse(t *testing.T) { number: 1, } - err := st.Block.SetHeader(testHeader) + err := st.Block.AddBlock(testBlock) require.NoError(t, err) err = gs.blockState.SetFinalizedHash(testHeader.Hash(), round, setID) diff --git a/lib/grandpa/message_test.go b/lib/grandpa/message_test.go index bc985cd45f..c94f14579f 100644 --- a/lib/grandpa/message_test.go +++ b/lib/grandpa/message_test.go @@ -1,11 +1,9 @@ package grandpa import ( - "math/big" "testing" "github.com/ChainSafe/gossamer/dot/state" - "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/crypto/ed25519" "github.com/ChainSafe/gossamer/lib/scale" @@ -102,16 +100,12 @@ func TestNewCatchUpResponse(t *testing.T) { round := uint64(1) setID := uint64(1) - testHeader := &types.Header{ - Number: big.NewInt(1), - } - v := &Vote{ hash: testHeader.Hash(), number: 1, } - err := st.Block.SetHeader(testHeader) + err := st.Block.AddBlock(testBlock) require.NoError(t, err) err = gs.blockState.SetFinalizedHash(testHeader.Hash(), round, setID) diff --git a/lib/grandpa/network_test.go b/lib/grandpa/network_test.go index e41d4b7d0b..d85dda093f 100644 --- a/lib/grandpa/network_test.go +++ b/lib/grandpa/network_test.go @@ -96,6 +96,9 @@ func TestSendNeighbourMessage(t *testing.T) { Header: &types.Header{ ParentHash: testGenesisHeader.Hash(), Number: big.NewInt(1), + Digest: types.Digest{ + types.NewBabeSecondaryPlainPreDigest(0, 1).ToPreRuntimeDigest(), + }, }, Body: &types.Body{}, } diff --git a/tests/rpc/rpc_01-system_test.go b/tests/rpc/rpc_01-system_test.go index b64641b1a0..48c4d2ab1d 100644 --- a/tests/rpc/rpc_01-system_test.go +++ b/tests/rpc/rpc_01-system_test.go @@ -60,7 +60,7 @@ func TestSystemRPC(t *testing.T) { expected: modules.SystemHealthResponse{ Peers: 2, - IsSyncing: true, + IsSyncing: false, ShouldHavePeers: true, }, params: "{}",