Skip to content

Commit a0a1804

Browse files
fix(dot/state/epoch, lib/babe): enable block production through epochs without rely on finalization (#2593)
* fix: block production to be independant of finalized blocks Co-authored-by: Timothy Wu <[email protected]>
1 parent 3d920cf commit a0a1804

File tree

9 files changed

+164
-341
lines changed

9 files changed

+164
-341
lines changed

dot/state/epoch.go

Lines changed: 77 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,16 @@ import (
1212

1313
"github.com/ChainSafe/chaindb"
1414
"github.com/ChainSafe/gossamer/dot/types"
15+
"github.com/ChainSafe/gossamer/lib/blocktree"
1516
"github.com/ChainSafe/gossamer/lib/common"
1617
"github.com/ChainSafe/gossamer/pkg/scale"
1718
)
1819

1920
var (
21+
ErrConfigNotFound = errors.New("config data not found")
2022
ErrEpochNotInMemory = errors.New("epoch not found in memory map")
2123
errHashNotInMemory = errors.New("hash not found in memory map")
22-
errEpochDataNotFound = errors.New("epoch data not found in the database")
24+
errEpochNotInDatabase = errors.New("epoch data not found in the database")
2325
errHashNotPersisted = errors.New("hash with next epoch not found in database")
2426
errNoPreRuntimeDigest = errors.New("header does not contain pre-runtime digest")
2527
)
@@ -58,11 +60,11 @@ type EpochState struct {
5860

5961
nextEpochDataLock sync.RWMutex
6062
// nextEpochData follows the format map[epoch]map[block hash]next epoch data
61-
nextEpochData map[uint64]map[common.Hash]types.NextEpochData
63+
nextEpochData nextEpochMap[types.NextEpochData]
6264

6365
nextConfigDataLock sync.RWMutex
6466
// nextConfigData follows the format map[epoch]map[block hash]next config data
65-
nextConfigData map[uint64]map[common.Hash]types.NextConfigData
67+
nextConfigData nextEpochMap[types.NextConfigData]
6668
}
6769

6870
// NewEpochStateFromGenesis returns a new EpochState given information for the first epoch, fetched from the runtime
@@ -90,8 +92,8 @@ func NewEpochStateFromGenesis(db chaindb.Database, blockState *BlockState,
9092
blockState: blockState,
9193
db: epochDB,
9294
epochLength: genesisConfig.EpochLength,
93-
nextEpochData: make(map[uint64]map[common.Hash]types.NextEpochData),
94-
nextConfigData: make(map[uint64]map[common.Hash]types.NextConfigData),
95+
nextEpochData: make(nextEpochMap[types.NextEpochData]),
96+
nextConfigData: make(nextEpochMap[types.NextConfigData]),
9597
}
9698

9799
auths, err := types.BABEAuthorityRawToAuthority(genesisConfig.GenesisAuthorities)
@@ -151,8 +153,8 @@ func NewEpochState(db chaindb.Database, blockState *BlockState) (*EpochState, er
151153
db: chaindb.NewTable(db, epochPrefix),
152154
epochLength: epochLength,
153155
skipToEpoch: skipToEpoch,
154-
nextEpochData: make(map[uint64]map[common.Hash]types.NextEpochData),
155-
nextConfigData: make(map[uint64]map[common.Hash]types.NextConfigData),
156+
nextEpochData: make(nextEpochMap[types.NextEpochData]),
157+
nextConfigData: make(nextEpochMap[types.NextConfigData]),
156158
}, nil
157159
}
158160

@@ -247,25 +249,29 @@ func (s *EpochState) SetEpochData(epoch uint64, info *types.EpochData) error {
247249
// if the header params is nil then it will search only in database
248250
func (s *EpochState) GetEpochData(epoch uint64, header *types.Header) (*types.EpochData, error) {
249251
epochData, err := s.getEpochDataFromDatabase(epoch)
250-
if err == nil && epochData != nil {
252+
if err != nil && !errors.Is(err, chaindb.ErrKeyNotFound) {
253+
return nil, fmt.Errorf("failed to retrieve epoch data from database: %w", err)
254+
}
255+
256+
if epochData != nil {
251257
return epochData, nil
252258
}
253259

254-
if err != nil && !errors.Is(err, chaindb.ErrKeyNotFound) {
255-
return nil, fmt.Errorf("failed to get epoch data from database: %w", err)
260+
if header == nil {
261+
return nil, errEpochNotInDatabase
256262
}
257263

258-
// lookup in-memory only if header is given
259-
if header != nil && errors.Is(err, chaindb.ErrKeyNotFound) {
260-
epochData, err = s.getEpochDataFromMemory(epoch, header)
261-
if err != nil {
262-
return nil, fmt.Errorf("failed to get epoch data from memory: %w", err)
263-
}
264+
s.nextEpochDataLock.RLock()
265+
defer s.nextEpochDataLock.RUnlock()
266+
267+
inMemoryEpochData, err := s.nextEpochData.Retrieve(s.blockState, epoch, header)
268+
if err != nil {
269+
return nil, fmt.Errorf("failed to get epoch data from memory: %w", err)
264270
}
265271

266-
if epochData == nil {
267-
return nil, fmt.Errorf("%w: for epoch %d and header with hash %s",
268-
errEpochDataNotFound, epoch, header.Hash())
272+
epochData, err = inMemoryEpochData.ToEpochData()
273+
if err != nil {
274+
return nil, fmt.Errorf("cannot transform into epoch data: %w", err)
269275
}
270276

271277
return epochData, nil
@@ -287,32 +293,6 @@ func (s *EpochState) getEpochDataFromDatabase(epoch uint64) (*types.EpochData, e
287293
return raw.ToEpochData()
288294
}
289295

290-
// getEpochDataFromMemory retrieves the right epoch data that belongs to the header parameter
291-
func (s *EpochState) getEpochDataFromMemory(epoch uint64, header *types.Header) (*types.EpochData, error) {
292-
s.nextEpochDataLock.RLock()
293-
defer s.nextEpochDataLock.RUnlock()
294-
295-
atEpoch, has := s.nextEpochData[epoch]
296-
if !has {
297-
return nil, fmt.Errorf("%w: %d", ErrEpochNotInMemory, epoch)
298-
}
299-
300-
headerHash := header.Hash()
301-
302-
for hash, value := range atEpoch {
303-
isDescendant, err := s.blockState.IsDescendantOf(hash, headerHash)
304-
if err != nil {
305-
return nil, fmt.Errorf("cannot verify the ancestry: %w", err)
306-
}
307-
308-
if isDescendant {
309-
return value.ToEpochData()
310-
}
311-
}
312-
313-
return nil, fmt.Errorf("%w: %s", errHashNotInMemory, headerHash)
314-
}
315-
316296
// GetLatestEpochData returns the EpochData for the current epoch
317297
func (s *EpochState) GetLatestEpochData() (*types.EpochData, error) {
318298
curr, err := s.GetCurrentEpoch()
@@ -323,26 +303,6 @@ func (s *EpochState) GetLatestEpochData() (*types.EpochData, error) {
323303
return s.GetEpochData(curr, nil)
324304
}
325305

326-
// HasEpochData returns whether epoch data exists for a given epoch
327-
func (s *EpochState) HasEpochData(epoch uint64) (bool, error) {
328-
has, err := s.db.Has(epochDataKey(epoch))
329-
if err == nil && has {
330-
return has, nil
331-
}
332-
333-
// we can have `has == false` and `err == nil`
334-
// so ensure the error is not nil in the condition below.
335-
if err != nil && !errors.Is(chaindb.ErrKeyNotFound, err) {
336-
return false, fmt.Errorf("cannot check database for epoch key %d: %w", epoch, err)
337-
}
338-
339-
s.nextEpochDataLock.Lock()
340-
defer s.nextEpochDataLock.Unlock()
341-
342-
_, has = s.nextEpochData[epoch]
343-
return has, nil
344-
}
345-
346306
// SetConfigData sets the BABE config data for a given epoch
347307
func (s *EpochState) SetConfigData(epoch uint64, info *types.ConfigData) error {
348308
enc, err := scale.Marshal(*info)
@@ -364,28 +324,44 @@ func (s *EpochState) setLatestConfigData(epoch uint64) error {
364324
return s.db.Put(latestConfigDataKey, buf)
365325
}
366326

367-
// GetConfigData returns the config data for a given epoch persisted in database
368-
// otherwise tries to get the data from the in-memory map using the header.
369-
// If the header params is nil then it will search only in the database
370-
func (s *EpochState) GetConfigData(epoch uint64, header *types.Header) (*types.ConfigData, error) {
371-
configData, err := s.getConfigDataFromDatabase(epoch)
372-
if err == nil && configData != nil {
373-
return configData, nil
374-
}
327+
// GetConfigData returns the newest config data for a given epoch persisted in database
328+
// otherwise tries to get the data from the in-memory map using the header. If we don't
329+
// find any config data for the current epoch we lookup in the previous epochs, as the spec says:
330+
// - The supplied configuration data are intended to be used from the next epoch onwards.
331+
// If the header params is nil then it will search only in the database.
332+
func (s *EpochState) GetConfigData(epoch uint64, header *types.Header) (configData *types.ConfigData, err error) {
333+
for tryEpoch := int(epoch); tryEpoch >= 0; tryEpoch-- {
334+
configData, err = s.getConfigDataFromDatabase(uint64(tryEpoch))
335+
if err != nil && !errors.Is(err, chaindb.ErrKeyNotFound) {
336+
return nil, fmt.Errorf("failed to retrieve config epoch from database: %w", err)
337+
}
375338

376-
if err != nil && !errors.Is(err, chaindb.ErrKeyNotFound) {
377-
return nil, fmt.Errorf("failed to get config data from database: %w", err)
378-
} else if header == nil {
379-
// if no header is given then skip the lookup in-memory
380-
return configData, nil
381-
}
339+
if configData != nil {
340+
return configData, nil
341+
}
382342

383-
configData, err = s.getConfigDataFromMemory(epoch, header)
384-
if err != nil {
385-
return nil, fmt.Errorf("failed to get config data from memory: %w", err)
343+
// there is no config data for the `tryEpoch` on database and we don't have a
344+
// header to lookup in the memory map, so let's go retrieve the previous epoch
345+
if header == nil {
346+
continue
347+
}
348+
349+
// we will check in the memory map and if we don't find the data
350+
// then we continue searching through the previous epoch
351+
s.nextConfigDataLock.RLock()
352+
inMemoryConfigData, err := s.nextConfigData.Retrieve(s.blockState, uint64(tryEpoch), header)
353+
s.nextConfigDataLock.RUnlock()
354+
355+
if errors.Is(err, ErrEpochNotInMemory) {
356+
continue
357+
} else if err != nil {
358+
return nil, fmt.Errorf("failed to get config data from memory: %w", err)
359+
}
360+
361+
return inMemoryConfigData.ToConfigData(), err
386362
}
387363

388-
return configData, nil
364+
return nil, fmt.Errorf("%w: epoch %d", ErrConfigNotFound, epoch)
389365
}
390366

391367
// getConfigDataFromDatabase returns the BABE config data for a given epoch persisted in database
@@ -404,26 +380,36 @@ func (s *EpochState) getConfigDataFromDatabase(epoch uint64) (*types.ConfigData,
404380
return info, nil
405381
}
406382

407-
// getConfigDataFromMemory retrieves the BABE config data for a given epoch that belongs to the header parameter
408-
func (s *EpochState) getConfigDataFromMemory(epoch uint64, header *types.Header) (*types.ConfigData, error) {
409-
s.nextConfigDataLock.RLock()
410-
defer s.nextConfigDataLock.RUnlock()
383+
type nextEpochMap[T types.NextEpochData | types.NextConfigData] map[uint64]map[common.Hash]T
411384

412-
atEpoch, has := s.nextConfigData[epoch]
385+
func (nem nextEpochMap[T]) Retrieve(blockState *BlockState, epoch uint64, header *types.Header) (*T, error) {
386+
atEpoch, has := nem[epoch]
413387
if !has {
414388
return nil, fmt.Errorf("%w: %d", ErrEpochNotInMemory, epoch)
415389
}
416390

417391
headerHash := header.Hash()
418-
419392
for hash, value := range atEpoch {
420-
isDescendant, err := s.blockState.IsDescendantOf(hash, headerHash)
393+
isDescendant, err := blockState.IsDescendantOf(hash, headerHash)
394+
395+
// sometimes while moving to the next epoch is possible the header
396+
// is not fully imported by the blocktree, in this case we will use
397+
// its parent header which migth be already imported.
398+
if errors.Is(err, blocktree.ErrEndNodeNotFound) {
399+
parentHeader, err := blockState.GetHeader(header.ParentHash)
400+
if err != nil {
401+
return nil, fmt.Errorf("cannot get parent header: %w", err)
402+
}
403+
404+
return nem.Retrieve(blockState, epoch, parentHeader)
405+
}
406+
421407
if err != nil {
422408
return nil, fmt.Errorf("cannot verify the ancestry: %w", err)
423409
}
424410

425411
if isDescendant {
426-
return value.ToConfigData(), nil
412+
return &value, nil
427413
}
428414
}
429415

@@ -441,24 +427,6 @@ func (s *EpochState) GetLatestConfigData() (*types.ConfigData, error) {
441427
return s.GetConfigData(epoch, nil)
442428
}
443429

444-
// HasConfigData returns whether config data exists for a given epoch
445-
func (s *EpochState) HasConfigData(epoch uint64) (bool, error) {
446-
has, err := s.db.Has(configDataKey(epoch))
447-
if err == nil && has {
448-
return has, nil
449-
}
450-
451-
if err != nil && !errors.Is(chaindb.ErrKeyNotFound, err) {
452-
return false, fmt.Errorf("cannot check database for epoch key %d: %w", epoch, err)
453-
}
454-
455-
s.nextConfigDataLock.Lock()
456-
defer s.nextConfigDataLock.Unlock()
457-
458-
_, has = s.nextConfigData[epoch]
459-
return has, nil
460-
}
461-
462430
// GetStartSlotForEpoch returns the first slot in the given epoch.
463431
// If 0 is passed as the epoch, it returns the start slot for the current epoch.
464432
func (s *EpochState) GetStartSlotForEpoch(epoch uint64) (uint64, error) {

dot/state/epoch_test.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,6 @@ func TestEpochState_CurrentEpoch(t *testing.T) {
5454

5555
func TestEpochState_EpochData(t *testing.T) {
5656
s := newEpochStateFromGenesis(t)
57-
has, err := s.HasEpochData(0)
58-
require.NoError(t, err)
59-
require.True(t, has)
6057

6158
keyring, err := keystore.NewSr25519Keyring()
6259
require.NoError(t, err)

0 commit comments

Comments
 (0)