Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(babe): Add support for versioned NextConfigData decoding #3239

Merged
merged 3 commits into from
May 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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")
dimartiro marked this conversation as resolved.
Show resolved Hide resolved

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