Skip to content

Commit

Permalink
fix(babe): Add support for versioned NextConfigData decoding (#3239)
Browse files Browse the repository at this point in the history
  • Loading branch information
dimartiro authored May 5, 2023
1 parent 5e11c9f commit 5ee3a64
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 40 deletions.
26 changes: 17 additions & 9 deletions dot/digest/digest.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ func (h *Handler) handleBabeConsensusDigest(digest scale.VaryingDataType, header
case types.NextEpochData:
currEpoch, err := h.epochState.GetEpochForBlock(header)
if err != nil {
return fmt.Errorf("cannot get epoch for block %d (%s): %w",
return fmt.Errorf("getting epoch for block %d (%s): %w",
header.Number, headerHash, err)
}

Expand All @@ -201,17 +201,25 @@ func (h *Handler) handleBabeConsensusDigest(digest scale.VaryingDataType, header
case types.BABEOnDisabled:
return nil

case types.NextConfigData:
currEpoch, err := h.epochState.GetEpochForBlock(header)
case types.VersionedNextConfigData:
nextConfigDataVersion, err := val.Value()
if err != nil {
return fmt.Errorf("cannot get epoch for block %d (%s): %w",
header.Number, headerHash, err)
return fmt.Errorf("getting digest value: %w", err)
}

nextEpoch := currEpoch + 1
h.epochState.StoreBABENextConfigData(nextEpoch, headerHash, val)
h.logger.Debugf("stored BABENextConfigData data: %v for hash: %s to epoch: %d", digest, headerHash, nextEpoch)
return nil
switch nextConfigData := nextConfigDataVersion.(type) {
case types.NextConfigDataV1:
currEpoch, err := h.epochState.GetEpochForBlock(header)
if err != nil {
return fmt.Errorf("getting epoch for block %d (%s): %w", header.Number, headerHash, err)
}
nextEpoch := currEpoch + 1
h.epochState.StoreBABENextConfigData(nextEpoch, headerHash, nextConfigData)
h.logger.Debugf("stored BABENextConfigData data: %v for hash: %s to epoch: %d", digest, headerHash, nextEpoch)
return nil
default:
return fmt.Errorf("next config data version not supported: %T", nextConfigDataVersion)
}
}

return errors.New("invalid consensus digest data")
Expand Down
19 changes: 15 additions & 4 deletions dot/digest/digest_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,13 +381,16 @@ func TestHandler_HandleNextEpochData(t *testing.T) {

func TestHandler_HandleNextConfigData(t *testing.T) {
var digest = types.NewBabeConsensusDigest()
nextConfigData := types.NextConfigData{
nextConfigData := types.NextConfigDataV1{
C1: 1,
C2: 8,
SecondarySlots: 1,
}

err := digest.Set(nextConfigData)
versionedNextConfigData := types.NewVersionedNextConfigData()
versionedNextConfigData.Set(nextConfigData)

err := digest.Set(versionedNextConfigData)
require.NoError(t, err)

data, err := scale.Marshal(digest)
Expand Down Expand Up @@ -428,12 +431,20 @@ func TestHandler_HandleNextConfigData(t *testing.T) {

digestValue, err := digest.Value()
require.NoError(t, err)
act, ok := digestValue.(types.NextConfigData)
nextVersionedConfigData, ok := digestValue.(types.VersionedNextConfigData)
if !ok {
t.Fatal()
}

decodedNextConfigData, err := nextVersionedConfigData.Value()
require.NoError(t, err)

decodedNextConfigDataV1, ok := decodedNextConfigData.(types.NextConfigDataV1)
if !ok {
t.Fatal()
}

stored, err := handler.epochState.(*state.EpochState).GetConfigData(targetEpoch, nil)
require.NoError(t, err)
require.Equal(t, act.ToConfigData(), stored)
require.Equal(t, decodedNextConfigDataV1.ToConfigData(), stored)
}
2 changes: 1 addition & 1 deletion dot/digest/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type BlockState interface {
type EpochState interface {
GetEpochForBlock(header *types.Header) (uint64, error)
StoreBABENextEpochData(epoch uint64, hash common.Hash, nextEpochData types.NextEpochData)
StoreBABENextConfigData(epoch uint64, hash common.Hash, nextEpochData types.NextConfigData)
StoreBABENextConfigData(epoch uint64, hash common.Hash, nextEpochData types.NextConfigDataV1)
FinalizeBABENextEpochData(finalizedHeader *types.Header) error
FinalizeBABENextConfigData(finalizedHeader *types.Header) error
}
Expand Down
2 changes: 1 addition & 1 deletion dot/digest/mock_epoch_state_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 7 additions & 7 deletions dot/state/epoch.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ type EpochState struct {

nextConfigDataLock sync.RWMutex
// nextConfigData follows the format map[epoch]map[block hash]next config data
nextConfigData nextEpochMap[types.NextConfigData]
nextConfigData nextEpochMap[types.NextConfigDataV1]
}

// NewEpochStateFromGenesis returns a new EpochState given information for the first epoch, fetched from the runtime
Expand Down Expand Up @@ -92,7 +92,7 @@ func NewEpochStateFromGenesis(db *chaindb.BadgerDB, blockState *BlockState,
db: epochDB,
epochLength: genesisConfig.EpochLength,
nextEpochData: make(nextEpochMap[types.NextEpochData]),
nextConfigData: make(nextEpochMap[types.NextConfigData]),
nextConfigData: make(nextEpochMap[types.NextConfigDataV1]),
}

auths, err := types.BABEAuthorityRawToAuthority(genesisConfig.GenesisAuthorities)
Expand Down Expand Up @@ -153,7 +153,7 @@ func NewEpochState(db *chaindb.BadgerDB, blockState *BlockState) (*EpochState, e
epochLength: epochLength,
skipToEpoch: skipToEpoch,
nextEpochData: make(nextEpochMap[types.NextEpochData]),
nextConfigData: make(nextEpochMap[types.NextConfigData]),
nextConfigData: make(nextEpochMap[types.NextConfigDataV1]),
}, nil
}

Expand Down Expand Up @@ -383,7 +383,7 @@ func (s *EpochState) getConfigDataFromDatabase(epoch uint64) (*types.ConfigData,
return info, nil
}

type nextEpochMap[T types.NextEpochData | types.NextConfigData] map[uint64]map[common.Hash]T
type nextEpochMap[T types.NextEpochData | types.NextConfigDataV1] map[uint64]map[common.Hash]T

func (nem nextEpochMap[T]) Retrieve(blockState *BlockState, epoch uint64, header *types.Header) (*T, error) {
atEpoch, has := nem[epoch]
Expand Down Expand Up @@ -505,13 +505,13 @@ func (s *EpochState) StoreBABENextEpochData(epoch uint64, hash common.Hash, next
}

// StoreBABENextConfigData stores the types.NextConfigData under epoch and hash keys
func (s *EpochState) StoreBABENextConfigData(epoch uint64, hash common.Hash, nextConfigData types.NextConfigData) {
func (s *EpochState) StoreBABENextConfigData(epoch uint64, hash common.Hash, nextConfigData types.NextConfigDataV1) {
s.nextConfigDataLock.Lock()
defer s.nextConfigDataLock.Unlock()

_, has := s.nextConfigData[epoch]
if !has {
s.nextConfigData[epoch] = make(map[common.Hash]types.NextConfigData)
s.nextConfigData[epoch] = make(map[common.Hash]types.NextConfigDataV1)
}
s.nextConfigData[epoch][hash] = nextConfigData
}
Expand Down Expand Up @@ -641,7 +641,7 @@ func (s *EpochState) FinalizeBABENextConfigData(finalizedHeader *types.Header) e
// findFinalizedHeaderForEpoch given a specific epoch (the key) will go through the hashes looking
// for a database persisted hash (belonging to the finalized chain)
// which contains the right configuration or data to be persisted and safely used
func findFinalizedHeaderForEpoch[T types.NextConfigData | types.NextEpochData](
func findFinalizedHeaderForEpoch[T types.NextConfigDataV1 | types.NextEpochData](
nextEpochMap map[uint64]map[common.Hash]T, es *EpochState, epoch uint64) (next *T, err error) {
hashes, has := nextEpochMap[epoch]
if !has {
Expand Down
16 changes: 8 additions & 8 deletions dot/state/epoch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) {

tests := map[string]struct {
finalizedHeader *types.Header
inMemoryEpoch []inMemoryBABEData[types.NextConfigData]
inMemoryEpoch []inMemoryBABEData[types.NextConfigDataV1]
finalizedEpoch uint64
expectErr error
shouldRemainInMemory int
Expand All @@ -455,15 +455,15 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) {
shouldRemainInMemory: 1,
finalizedEpoch: 2,
finalizedHeader: finalizedHeader,
inMemoryEpoch: []inMemoryBABEData[types.NextConfigData]{
inMemoryEpoch: []inMemoryBABEData[types.NextConfigDataV1]{
{
epoch: 1,
hashes: []common.Hash{
common.MustHexToHash("0x9da3ce2785da743bfbc13449db7dcb7a69c07ca914276d839abe7bedc6ac8fed"),
common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"),
common.MustHexToHash("0xc0096358534ec8d21d01d34b836eed476a1c343f8724fa2153dc0725ad797a90"),
},
nextData: []types.NextConfigData{
nextData: []types.NextConfigDataV1{
{
C1: 1,
C2: 2,
Expand All @@ -488,7 +488,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) {
common.MustHexToHash("0xd380bee22de487a707cbda65dd9d4e2188f736908c42cf390c8919d4f7fc547c"),
finalizedHeaderHash,
},
nextData: []types.NextConfigData{
nextData: []types.NextConfigDataV1{
{
C1: 1,
C2: 2,
Expand All @@ -511,7 +511,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) {
hashes: []common.Hash{
common.MustHexToHash("0xab5c9230a7dde8bb90a6728ba4a0165423294dac14336b1443f865b796ff682c"),
},
nextData: []types.NextConfigData{
nextData: []types.NextConfigDataV1{
{
C1: 1,
C2: 2,
Expand All @@ -526,15 +526,15 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) {
finalizedEpoch: 2,
finalizedHeader: finalizedHeader, // finalize when the hash does not exist
expectErr: errHashNotPersisted,
inMemoryEpoch: []inMemoryBABEData[types.NextConfigData]{
inMemoryEpoch: []inMemoryBABEData[types.NextConfigDataV1]{
{
epoch: 2,
hashes: []common.Hash{
common.MustHexToHash("0x9da3ce2785da743bfbc13449db7dcb7a69c07ca914276d839abe7bedc6ac8fed"),
common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"),
common.MustHexToHash("0xc0096358534ec8d21d01d34b836eed476a1c343f8724fa2153dc0725ad797a90"),
},
nextData: []types.NextConfigData{
nextData: []types.NextConfigDataV1{
{
C1: 1,
C2: 2,
Expand All @@ -558,7 +558,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) {
shouldRemainInMemory: 0,
finalizedEpoch: 1, // try to finalize an epoch that does not exist
finalizedHeader: finalizedHeader,
inMemoryEpoch: []inMemoryBABEData[types.NextConfigData]{},
inMemoryEpoch: []inMemoryBABEData[types.NextConfigDataV1]{},
},
}

Expand Down
52 changes: 46 additions & 6 deletions dot/types/consensus_digest.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

// NewBabeConsensusDigest constructs a vdt representing a babe consensus digest
func NewBabeConsensusDigest() scale.VaryingDataType {
return scale.MustNewVaryingDataType(NextEpochData{}, BABEOnDisabled{}, NextConfigData{})
return scale.MustNewVaryingDataType(NextEpochData{}, BABEOnDisabled{}, NewVersionedNextConfigData())
}

// NewGrandpaConsensusDigest constructs a vdt representing a grandpa consensus digest
Expand Down Expand Up @@ -126,27 +126,67 @@ func (b BABEOnDisabled) String() string {
return fmt.Sprintf("BABEOnDisabled{ID=%d}", b.ID)
}

// NextConfigData is the digest that contains changes to the BABE configuration.
// NextConfigDataV1 is the digest that contains changes to the BABE configuration.
// It is potentially included in the first block of an epoch to describe the next epoch.
type NextConfigData struct {
type NextConfigDataV1 struct {
C1 uint64
C2 uint64
SecondarySlots byte
}

// Index returns VDT index
func (NextConfigData) Index() uint { return 3 } //skipcq: GO-W1029
func (NextConfigDataV1) Index() uint { return 1 } //skipcq: GO-W1029

func (d NextConfigData) String() string { //skipcq: GO-W1029
func (d NextConfigDataV1) String() string { //skipcq: GO-W1029
return fmt.Sprintf("NextConfigData{C1=%d, C2=%d, SecondarySlots=%d}",
d.C1, d.C2, d.SecondarySlots)
}

// ToConfigData returns the NextConfigData as ConfigData
func (d *NextConfigData) ToConfigData() *ConfigData { //skipcq: GO-W1029
func (d *NextConfigDataV1) ToConfigData() *ConfigData { //skipcq: GO-W1029
return &ConfigData{
C1: d.C1,
C2: d.C2,
SecondarySlots: d.SecondarySlots,
}
}

// VersionedNextConfigData represents the enum of next config data consensus digest messages
type VersionedNextConfigData scale.VaryingDataType

// Index returns VDT index
func (VersionedNextConfigData) Index() uint { return 3 }

// Value returns the current VDT value
func (vncd *VersionedNextConfigData) Value() (val scale.VaryingDataTypeValue, err error) {
vdt := scale.VaryingDataType(*vncd)
return vdt.Value()
}

// Set updates the current VDT value to be `val`
func (vncd *VersionedNextConfigData) Set(val scale.VaryingDataTypeValue) (err error) {
vdt := scale.VaryingDataType(*vncd)
err = vdt.Set(val)
if err != nil {
return fmt.Errorf("setting varying data type value: %w", err)
}
*vncd = VersionedNextConfigData(vdt)
return nil
}

// String returns the string representation for the current VDT value
func (vncd VersionedNextConfigData) String() string {
val, err := vncd.Value()
if err != nil {
return "VersionedNextConfigData()"
}

return fmt.Sprintf("VersionedNextConfigData(%s)", val)
}

// NewVersionedNextConfigData creates a new VersionedNextConfigData instance
func NewVersionedNextConfigData() VersionedNextConfigData {
vdt := scale.MustNewVaryingDataType(NextConfigDataV1{})

return VersionedNextConfigData(vdt)
}
23 changes: 23 additions & 0 deletions dot/types/consensus_digest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,26 @@ func TestBabeEncodeAndDecode(t *testing.T) {
require.NoError(t, err)
require.Equal(t, d, dec)
}

func TestBabeDecodeVersionedNextConfigData(t *testing.T) {
// Block #5608275 NextConfigData digest
enc := common.MustHexToBytes("0x03010100000000000000040000000000000002")

var dec = NewBabeConsensusDigest()
err := scale.Unmarshal(enc, &dec)
require.NoError(t, err)

decValue, err := dec.Value()
require.NoError(t, err)

nextVersionedConfigData := decValue.(VersionedNextConfigData)

nextConfigData, err := nextVersionedConfigData.Value()
require.NoError(t, err)

nextConfigDataV1 := nextConfigData.(NextConfigDataV1)

require.GreaterOrEqual(t, 1, int(nextConfigDataV1.C1))
require.GreaterOrEqual(t, 4, int(nextConfigDataV1.C2))
require.GreaterOrEqual(t, 2, int(nextConfigDataV1.SecondarySlots))
}
12 changes: 8 additions & 4 deletions lib/babe/verify_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ func TestVerifyForkBlocksWithRespectiveEpochData(t *testing.T) {
aliceBlockNextEpoch := types.NextEpochData{
Authorities: authorities[3:],
}
aliceBlockNextConfigData := types.NextConfigData{
aliceBlockNextConfigData := types.NextConfigDataV1{
C1: 9,
C2: 10,
SecondarySlots: 1,
Expand All @@ -555,7 +555,7 @@ func TestVerifyForkBlocksWithRespectiveEpochData(t *testing.T) {
bobBlockNextEpoch := types.NextEpochData{
Authorities: authorities[6:],
}
bobBlockNextConfigData := types.NextConfigData{
bobBlockNextConfigData := types.NextConfigDataV1{
C1: 3,
C2: 8,
SecondarySlots: 1,
Expand Down Expand Up @@ -672,7 +672,7 @@ func TestVerifyForkBlocksWithRespectiveEpochData(t *testing.T) {
// blocks that contains different consensus messages digests
func issueConsensusDigestsBlockFromGenesis(t *testing.T, genesisHeader *types.Header,
kp *sr25519.Keypair, stateService *state.Service,
nextEpoch types.NextEpochData, nextConfig types.NextConfigData) *types.Header {
nextEpoch types.NextEpochData, nextConfig types.NextConfigDataV1) *types.Header {
t.Helper()

output, proof, err := kp.VrfSign(makeTranscript(Randomness{}, uint64(0), 0))
Expand All @@ -691,7 +691,11 @@ func issueConsensusDigestsBlockFromGenesis(t *testing.T, genesisHeader *types.He
require.NoError(t, babeConsensusDigestNextEpoch.Set(nextEpoch))

babeConsensusDigestNextConfigData := types.NewBabeConsensusDigest()
require.NoError(t, babeConsensusDigestNextConfigData.Set(nextConfig))

versionedNextConfigData := types.NewVersionedNextConfigData()
versionedNextConfigData.Set(nextConfig)

require.NoError(t, babeConsensusDigestNextConfigData.Set(versionedNextConfigData))

nextEpochData, err := scale.Marshal(babeConsensusDigestNextEpoch)
require.NoError(t, err)
Expand Down

0 comments on commit 5ee3a64

Please sign in to comment.