diff --git a/dot/core/digest.go b/dot/core/digest.go
index a52d124a26..64a61201a7 100644
--- a/dot/core/digest.go
+++ b/dot/core/digest.go
@@ -33,13 +33,11 @@ type DigestHandler struct {
cancel context.CancelFunc
// interfaces
- blockState BlockState
- epochState EpochState
- grandpa FinalityGadget
- babe BlockProducer
- verifier Verifier
- isFinalityAuthority bool
- isBlockProducer bool
+ blockState BlockState
+ epochState EpochState
+ grandpaState GrandpaState
+ babe BlockProducer
+ verifier Verifier
// block notification channels
imported chan *types.Block
@@ -52,7 +50,6 @@ type DigestHandler struct {
grandpaForcedChange *grandpaChange
grandpaPause *pause
grandpaResume *resume
- grandpaAuths []*types.Authority // saved in case of pause
}
type grandpaChange struct {
@@ -69,7 +66,7 @@ type resume struct {
}
// NewDigestHandler returns a new DigestHandler
-func NewDigestHandler(blockState BlockState, epochState EpochState, babe BlockProducer, grandpa FinalityGadget, verifier Verifier) (*DigestHandler, error) {
+func NewDigestHandler(blockState BlockState, epochState EpochState, grandpaState GrandpaState, babe BlockProducer, verifier Verifier) (*DigestHandler, error) {
imported := make(chan *types.Block, 16)
finalised := make(chan *types.Header, 16)
iid, err := blockState.RegisterImportedChannel(imported)
@@ -82,32 +79,27 @@ func NewDigestHandler(blockState BlockState, epochState EpochState, babe BlockPr
return nil, err
}
- isFinalityAuthority := grandpa != nil
- isBlockProducer := babe != nil
-
ctx, cancel := context.WithCancel(context.Background())
return &DigestHandler{
- ctx: ctx,
- cancel: cancel,
- blockState: blockState,
- epochState: epochState,
- grandpa: grandpa,
- babe: babe,
- verifier: verifier,
- isFinalityAuthority: isFinalityAuthority,
- isBlockProducer: isBlockProducer,
- imported: imported,
- importedID: iid,
- finalised: finalised,
- finalisedID: fid,
+ ctx: ctx,
+ cancel: cancel,
+ blockState: blockState,
+ epochState: epochState,
+ grandpaState: grandpaState,
+ babe: babe,
+ verifier: verifier,
+ imported: imported,
+ importedID: iid,
+ finalised: finalised,
+ finalisedID: fid,
}, nil
}
// Start starts the DigestHandler
func (h *DigestHandler) Start() {
go h.handleBlockImport(h.ctx)
- go h.handleBlockFinalization(h.ctx)
+ go h.handleBlockFinalisation(h.ctx)
}
// Stop stops the DigestHandler
@@ -119,11 +111,6 @@ func (h *DigestHandler) Stop() {
close(h.finalised)
}
-// SetFinalityGadget sets the digest handler's grandpa instance
-func (h *DigestHandler) SetFinalityGadget(grandpa FinalityGadget) {
- h.grandpa = grandpa
-}
-
// NextGrandpaAuthorityChange returns the block number of the next upcoming grandpa authorities change.
// It returns 0 if no change is scheduled.
func (h *DigestHandler) NextGrandpaAuthorityChange() uint64 {
@@ -155,11 +142,9 @@ func (h *DigestHandler) HandleConsensusDigest(d *types.ConsensusDigest, header *
if d.ConsensusEngineID == types.GrandpaEngineID {
switch t {
case types.GrandpaScheduledChangeType:
- return h.handleScheduledChange(d)
+ return h.handleScheduledChange(d, header)
case types.GrandpaForcedChangeType:
- return h.handleForcedChange(d)
- case types.GrandpaOnDisabledType:
- return h.handleGrandpaOnDisabled(d, header)
+ return h.handleForcedChange(d, header)
case types.GrandpaPauseType:
return h.handlePause(d)
case types.GrandpaResumeType:
@@ -193,8 +178,9 @@ func (h *DigestHandler) handleBlockImport(ctx context.Context) {
continue
}
- if h.isFinalityAuthority {
- h.handleGrandpaChangesOnImport(block.Header.Number)
+ err := h.handleGrandpaChangesOnImport(block.Header.Number)
+ if err != nil {
+ logger.Error("failed to handle grandpa changes on block import", "error", err)
}
case <-ctx.Done():
return
@@ -202,7 +188,7 @@ func (h *DigestHandler) handleBlockImport(ctx context.Context) {
}
}
-func (h *DigestHandler) handleBlockFinalization(ctx context.Context) {
+func (h *DigestHandler) handleBlockFinalisation(ctx context.Context) {
for {
select {
case header := <-h.finalised:
@@ -210,146 +196,132 @@ func (h *DigestHandler) handleBlockFinalization(ctx context.Context) {
continue
}
- if h.isFinalityAuthority {
- h.handleGrandpaChangesOnFinalization(header.Number)
+ err := h.handleGrandpaChangesOnFinalization(header.Number)
+ if err != nil {
+ logger.Error("failed to handle grandpa changes on block finalisation", "error", err)
}
-
- // TODO: check if there's a NextEpochData or NextConfigData digest, if there is,
- // make sure it matches what's in the EpochState for the upcoming epoch
case <-ctx.Done():
return
}
}
}
-func (h *DigestHandler) handleGrandpaChangesOnImport(num *big.Int) {
+func (h *DigestHandler) handleGrandpaChangesOnImport(num *big.Int) error {
resume := h.grandpaResume
if resume != nil && num.Cmp(resume.atBlock) == 0 {
- h.grandpa.UpdateAuthorities(h.grandpaAuths)
h.grandpaResume = nil
}
fc := h.grandpaForcedChange
if fc != nil && num.Cmp(fc.atBlock) == 0 {
- h.grandpa.UpdateAuthorities(fc.auths)
+ err := h.grandpaState.IncrementSetID()
+ if err != nil {
+ return err
+ }
+
h.grandpaForcedChange = nil
}
+
+ return nil
}
-func (h *DigestHandler) handleGrandpaChangesOnFinalization(num *big.Int) {
+func (h *DigestHandler) handleGrandpaChangesOnFinalization(num *big.Int) error {
pause := h.grandpaPause
if pause != nil && num.Cmp(pause.atBlock) == 0 {
- // save authority data for Resume
- h.grandpaAuths = h.grandpa.Authorities()
- h.grandpa.UpdateAuthorities([]*types.Authority{})
h.grandpaPause = nil
}
sc := h.grandpaScheduledChange
if sc != nil && num.Cmp(sc.atBlock) == 0 {
- h.grandpa.UpdateAuthorities(sc.auths)
+ err := h.grandpaState.IncrementSetID()
+ if err != nil {
+ return err
+ }
+
h.grandpaScheduledChange = nil
}
// if blocks get finalised before forced change takes place, disregard it
h.grandpaForcedChange = nil
+ return nil
}
-func (h *DigestHandler) handleScheduledChange(d *types.ConsensusDigest) error {
+func (h *DigestHandler) handleScheduledChange(d *types.ConsensusDigest, header *types.Header) error {
curr, err := h.blockState.BestBlockHeader()
if err != nil {
return err
}
- if d.ConsensusEngineID == types.GrandpaEngineID {
- if h.grandpaScheduledChange != nil {
- return nil
- }
+ if d.ConsensusEngineID != types.GrandpaEngineID {
+ return nil
+ }
- sc := &types.GrandpaScheduledChange{}
- dec, err := scale.Decode(d.Data[1:], sc)
- if err != nil {
- return err
- }
- sc = dec.(*types.GrandpaScheduledChange)
+ if h.grandpaScheduledChange != nil {
+ return nil
+ }
- logger.Debug("handling GrandpaScheduledChange", "data", sc)
+ sc := &types.GrandpaScheduledChange{}
+ dec, err := scale.Decode(d.Data[1:], sc)
+ if err != nil {
+ return err
+ }
+ sc = dec.(*types.GrandpaScheduledChange)
- if h.grandpa == nil {
- // this should never happen
- return nil
- }
+ logger.Debug("handling GrandpaScheduledChange", "data", sc)
- c, err := newGrandpaChange(sc.Auths, sc.Delay, curr.Number)
- if err != nil {
- return err
- }
-
- h.grandpaScheduledChange = c
+ c, err := newGrandpaChange(sc.Auths, sc.Delay, curr.Number)
+ if err != nil {
+ return err
}
- return nil
-}
+ h.grandpaScheduledChange = c
-func (h *DigestHandler) handleForcedChange(d *types.ConsensusDigest) error {
- curr, err := h.blockState.BestBlockHeader()
+ auths, err := types.GrandpaAuthoritiesRawToAuthorities(sc.Auths)
if err != nil {
return err
}
- if d.ConsensusEngineID == types.GrandpaEngineID {
- if h.grandpaForcedChange != nil {
- return errors.New("already have forced change scheduled")
- }
-
- fc := &types.GrandpaForcedChange{}
- dec, err := scale.Decode(d.Data[1:], fc)
- if err != nil {
- return err
- }
- fc = dec.(*types.GrandpaForcedChange)
+ return h.grandpaState.SetNextChange(types.NewGrandpaVotersFromAuthorities(auths), big.NewInt(0).Add(header.Number, big.NewInt(int64(sc.Delay))))
+}
- c, err := newGrandpaChange(fc.Auths, fc.Delay, curr.Number)
- if err != nil {
- return err
- }
+func (h *DigestHandler) handleForcedChange(d *types.ConsensusDigest, header *types.Header) error {
+ if d.ConsensusEngineID != types.GrandpaEngineID {
+ return nil
+ }
- h.grandpaForcedChange = c
+ if header == nil {
+ return errors.New("header is nil")
}
- return nil
-}
+ if h.grandpaForcedChange != nil {
+ return errors.New("already have forced change scheduled")
+ }
-func (h *DigestHandler) handleGrandpaOnDisabled(d *types.ConsensusDigest, _ *types.Header) error {
- od := &types.GrandpaOnDisabled{}
- dec, err := scale.Decode(d.Data[1:], od)
+ fc := &types.GrandpaForcedChange{}
+ dec, err := scale.Decode(d.Data[1:], fc)
if err != nil {
return err
}
- od = dec.(*types.GrandpaOnDisabled)
+ fc = dec.(*types.GrandpaForcedChange)
- logger.Debug("handling GrandpaOnDisabled", "data", od)
+ logger.Debug("handling GrandpaForcedChange", "data", fc)
- if h.grandpa == nil {
- // this should never happen
- return nil
+ c, err := newGrandpaChange(fc.Auths, fc.Delay, header.Number)
+ if err != nil {
+ return err
}
- curr := h.grandpa.Authorities()
- next := []*types.Authority{}
+ h.grandpaForcedChange = c
- for _, auth := range curr {
- if auth.Weight != od.ID {
- next = append(next, auth)
- }
+ auths, err := types.GrandpaAuthoritiesRawToAuthorities(fc.Auths)
+ if err != nil {
+ return err
}
- // TODO: this needs to be updated not to remove the authority from the list,
- // but to flag them as disabled. thus, if we are disabled, we should stop voting.
- // if we receive vote or finalisation messages, we should ignore anything signed by the
- // disabled authority
- h.grandpa.UpdateAuthorities(next)
- return nil
+ return h.grandpaState.SetNextChange(
+ types.NewGrandpaVotersFromAuthorities(auths),
+ big.NewInt(0).Add(header.Number, big.NewInt(int64(fc.Delay))),
+ )
}
func (h *DigestHandler) handlePause(d *types.ConsensusDigest) error {
@@ -371,7 +343,7 @@ func (h *DigestHandler) handlePause(d *types.ConsensusDigest) error {
atBlock: big.NewInt(-1).Add(curr.Number, delay),
}
- return nil
+ return h.grandpaState.SetNextPause(h.grandpaPause.atBlock)
}
func (h *DigestHandler) handleResume(d *types.ConsensusDigest) error {
@@ -393,7 +365,7 @@ func (h *DigestHandler) handleResume(d *types.ConsensusDigest) error {
atBlock: big.NewInt(-1).Add(curr.Number, delay),
}
- return nil
+ return h.grandpaState.SetNextResume(h.grandpaResume.atBlock)
}
func newGrandpaChange(raw []*types.GrandpaAuthoritiesRaw, delay uint32, currBlock *big.Int) (*grandpaChange, error) {
diff --git a/dot/core/digest_test.go b/dot/core/digest_test.go
index 396e24758e..51f78e11ff 100644
--- a/dot/core/digest_test.go
+++ b/dot/core/digest_test.go
@@ -18,6 +18,7 @@ package core
import (
"io/ioutil"
+ "math/big"
"testing"
"time"
@@ -49,13 +50,8 @@ func newTestDigestHandler(t *testing.T, withBABE, withGrandpa bool) *DigestHandl
bp = &mockBlockProducer{}
}
- var fg FinalityGadget
- if withGrandpa {
- fg = &mockFinalityGadget{}
- }
-
time.Sleep(time.Second)
- dh, err := NewDigestHandler(stateSrvc.Block, stateSrvc.Epoch, bp, fg, &mockVerifier{})
+ dh, err := NewDigestHandler(stateSrvc.Block, stateSrvc.Epoch, stateSrvc.Grandpa, bp, &mockVerifier{})
require.NoError(t, err)
return dh
}
@@ -64,7 +60,6 @@ func TestDigestHandler_GrandpaScheduledChange(t *testing.T) {
handler := newTestDigestHandler(t, false, true)
handler.Start()
defer handler.Stop()
- require.True(t, handler.isFinalityAuthority)
kr, err := keystore.NewEd25519Keyring()
require.NoError(t, err)
@@ -84,7 +79,11 @@ func TestDigestHandler_GrandpaScheduledChange(t *testing.T) {
Data: data,
}
- err = handler.HandleConsensusDigest(d, nil)
+ header := &types.Header{
+ Number: big.NewInt(1),
+ }
+
+ err = handler.HandleConsensusDigest(d, header)
require.NoError(t, err)
headers := addTestBlocksToState(t, 2, handler.blockState)
@@ -92,9 +91,6 @@ func TestDigestHandler_GrandpaScheduledChange(t *testing.T) {
handler.blockState.SetFinalizedHash(h.Hash(), 0, 0)
}
- auths := handler.grandpa.Authorities()
- require.Nil(t, auths)
-
// authorities should change on start of block 3 from start
headers = addTestBlocksToState(t, 1, handler.blockState)
for _, h := range headers {
@@ -102,8 +98,15 @@ func TestDigestHandler_GrandpaScheduledChange(t *testing.T) {
}
time.Sleep(time.Millisecond * 100)
- auths = handler.grandpa.Authorities()
- require.Equal(t, 1, len(auths))
+ setID, err := handler.grandpaState.(*state.GrandpaState).GetCurrentSetID()
+ require.NoError(t, err)
+ require.Equal(t, uint64(1), setID)
+
+ auths, err := handler.grandpaState.(*state.GrandpaState).GetAuthorities(setID)
+ require.NoError(t, err)
+ expected, err := types.NewGrandpaVotersFromAuthoritiesRaw(sc.Auths)
+ require.NoError(t, err)
+ require.Equal(t, expected, auths)
}
func TestDigestHandler_GrandpaForcedChange(t *testing.T) {
@@ -129,69 +132,28 @@ func TestDigestHandler_GrandpaForcedChange(t *testing.T) {
Data: data,
}
- err = handler.HandleConsensusDigest(d, nil)
+ header := &types.Header{
+ Number: big.NewInt(1),
+ }
+
+ err = handler.HandleConsensusDigest(d, header)
require.NoError(t, err)
- addTestBlocksToState(t, 2, handler.blockState)
- auths := handler.grandpa.Authorities()
- require.Nil(t, auths)
+ addTestBlocksToState(t, 3, handler.blockState)
- // authorities should change on start of block 3 from start
+ // authorities should change on start of block 4 from start
addTestBlocksToState(t, 1, handler.blockState)
time.Sleep(time.Millisecond * 100)
- auths = handler.grandpa.Authorities()
- require.Equal(t, 1, len(auths))
-}
-
-func TestDigestHandler_GrandpaOnDisabled(t *testing.T) {
- handler := newTestDigestHandler(t, false, true)
- handler.Start()
- defer handler.Stop()
-
- kr, err := keystore.NewEd25519Keyring()
- require.NoError(t, err)
- handler.grandpa.UpdateAuthorities([]*types.Authority{
- {Key: kr.Alice().Public().(*ed25519.PublicKey), Weight: 0},
- })
-
- // try with ID that doesn't exist
- od := &types.GrandpaOnDisabled{
- ID: 1,
- }
-
- data, err := od.Encode()
+ setID, err := handler.grandpaState.(*state.GrandpaState).GetCurrentSetID()
require.NoError(t, err)
+ require.Equal(t, uint64(1), setID)
- d := &types.ConsensusDigest{
- ConsensusEngineID: types.GrandpaEngineID,
- Data: data,
- }
-
- err = handler.HandleConsensusDigest(d, nil)
+ auths, err := handler.grandpaState.(*state.GrandpaState).GetAuthorities(setID)
require.NoError(t, err)
-
- auths := handler.grandpa.Authorities()
- require.Equal(t, 1, len(auths))
-
- // try with ID that does exist
- od = &types.GrandpaOnDisabled{
- ID: 0,
- }
-
- data, err = od.Encode()
+ expected, err := types.NewGrandpaVotersFromAuthoritiesRaw(fc.Auths)
require.NoError(t, err)
-
- d = &types.ConsensusDigest{
- ConsensusEngineID: types.GrandpaEngineID,
- Data: data,
- }
-
- err = handler.HandleConsensusDigest(d, nil)
- require.NoError(t, err)
-
- auths = handler.grandpa.Authorities()
- require.Equal(t, 0, len(auths))
+ require.Equal(t, expected, auths)
}
func TestDigestHandler_GrandpaPauseAndResume(t *testing.T) {
@@ -199,13 +161,6 @@ func TestDigestHandler_GrandpaPauseAndResume(t *testing.T) {
handler.Start()
defer handler.Stop()
- kr, err := keystore.NewEd25519Keyring()
- require.NoError(t, err)
-
- handler.grandpa.UpdateAuthorities([]*types.Authority{
- {Key: kr.Alice().Public().(*ed25519.PublicKey), Weight: 0},
- })
-
p := &types.GrandpaPause{
Delay: 3,
}
@@ -220,6 +175,9 @@ func TestDigestHandler_GrandpaPauseAndResume(t *testing.T) {
err = handler.HandleConsensusDigest(d, nil)
require.NoError(t, err)
+ nextPause, err := handler.grandpaState.(*state.GrandpaState).GetNextPause()
+ require.NoError(t, err)
+ require.Equal(t, big.NewInt(int64(p.Delay)), nextPause)
headers := addTestBlocksToState(t, 3, handler.blockState)
for _, h := range headers {
@@ -227,8 +185,7 @@ func TestDigestHandler_GrandpaPauseAndResume(t *testing.T) {
}
time.Sleep(time.Millisecond * 100)
- auths := handler.grandpa.Authorities()
- require.Equal(t, 0, len(auths))
+ require.Nil(t, handler.grandpaPause)
r := &types.GrandpaResume{
Delay: 3,
@@ -247,8 +204,11 @@ func TestDigestHandler_GrandpaPauseAndResume(t *testing.T) {
addTestBlocksToState(t, 3, handler.blockState)
time.Sleep(time.Millisecond * 110)
- auths = handler.grandpa.Authorities()
- require.Equal(t, 1, len(auths))
+ require.Nil(t, handler.grandpaResume)
+
+ nextResume, err := handler.grandpaState.(*state.GrandpaState).GetNextResume()
+ require.NoError(t, err)
+ require.Equal(t, big.NewInt(int64(r.Delay)+int64(p.Delay)), nextResume)
}
func TestNextGrandpaAuthorityChange_OneChange(t *testing.T) {
@@ -269,12 +229,22 @@ func TestNextGrandpaAuthorityChange_OneChange(t *testing.T) {
ConsensusEngineID: types.GrandpaEngineID,
Data: data,
}
+ header := &types.Header{
+ Number: big.NewInt(1),
+ }
- err = handler.HandleConsensusDigest(d, nil)
+ err = handler.HandleConsensusDigest(d, header)
require.NoError(t, err)
next := handler.NextGrandpaAuthorityChange()
require.Equal(t, uint64(block), next)
+
+ nextSetID := uint64(1)
+ auths, err := handler.grandpaState.(*state.GrandpaState).GetAuthorities(nextSetID)
+ require.NoError(t, err)
+ expected, err := types.NewGrandpaVotersFromAuthoritiesRaw(sc.Auths)
+ require.NoError(t, err)
+ require.Equal(t, expected, auths)
}
func TestNextGrandpaAuthorityChange_MultipleChanges(t *testing.T) {
@@ -282,7 +252,10 @@ func TestNextGrandpaAuthorityChange_MultipleChanges(t *testing.T) {
handler.Start()
defer handler.Stop()
- later := uint32(5)
+ kr, err := keystore.NewEd25519Keyring()
+ require.NoError(t, err)
+
+ later := uint32(6)
sc := &types.GrandpaScheduledChange{
Auths: []*types.GrandpaAuthoritiesRaw{},
Delay: later,
@@ -296,12 +269,25 @@ func TestNextGrandpaAuthorityChange_MultipleChanges(t *testing.T) {
Data: data,
}
- err = handler.HandleConsensusDigest(d, nil)
+ header := &types.Header{
+ Number: big.NewInt(1),
+ }
+
+ err = handler.HandleConsensusDigest(d, header)
+ require.NoError(t, err)
+
+ nextSetID := uint64(1)
+ auths, err := handler.grandpaState.(*state.GrandpaState).GetAuthorities(nextSetID)
+ require.NoError(t, err)
+ expected, err := types.NewGrandpaVotersFromAuthoritiesRaw(sc.Auths)
require.NoError(t, err)
+ require.Equal(t, expected, auths)
- earlier := uint32(3)
+ earlier := uint32(4)
fc := &types.GrandpaForcedChange{
- Auths: []*types.GrandpaAuthoritiesRaw{},
+ Auths: []*types.GrandpaAuthoritiesRaw{
+ {Key: kr.Alice().Public().(*ed25519.PublicKey).AsBytes(), ID: 0},
+ },
Delay: earlier,
}
@@ -313,11 +299,17 @@ func TestNextGrandpaAuthorityChange_MultipleChanges(t *testing.T) {
Data: data,
}
- err = handler.HandleConsensusDigest(d, nil)
+ err = handler.HandleConsensusDigest(d, header)
require.NoError(t, err)
next := handler.NextGrandpaAuthorityChange()
- require.Equal(t, uint64(earlier), next)
+ require.Equal(t, uint64(earlier+1), next)
+
+ auths, err = handler.grandpaState.(*state.GrandpaState).GetAuthorities(nextSetID)
+ require.NoError(t, err)
+ expected, err = types.NewGrandpaVotersFromAuthoritiesRaw(fc.Auths)
+ require.NoError(t, err)
+ require.Equal(t, expected, auths)
}
func TestDigestHandler_HandleBABEOnDisabled(t *testing.T) {
diff --git a/dot/core/errors.go b/dot/core/errors.go
index 0e33e5fdf1..5faf6f8b02 100644
--- a/dot/core/errors.go
+++ b/dot/core/errors.go
@@ -45,9 +45,6 @@ var ErrNilRuntime = errors.New("cannot have nil runtime")
// ErrNilBlockProducer is returned when trying to instantiate a block producing Service without a block producer
var ErrNilBlockProducer = errors.New("cannot have nil BlockProducer")
-// ErrNilFinalityGadget is returned when trying to instantiate a finalising Service without a finality gadget
-var ErrNilFinalityGadget = errors.New("cannot have nil FinalityGadget")
-
// ErrNilConsensusMessageHandler is returned when trying to instantiate a Service without a FinalityMessageHandler
var ErrNilConsensusMessageHandler = errors.New("cannot have nil ErrNilFinalityMessageHandler")
diff --git a/dot/core/interface.go b/dot/core/interface.go
index e6c2d43759..5c1cd1e52c 100644
--- a/dot/core/interface.go
+++ b/dot/core/interface.go
@@ -22,8 +22,8 @@ import (
"github.com/ChainSafe/gossamer/dot/network"
"github.com/ChainSafe/gossamer/dot/types"
"github.com/ChainSafe/gossamer/lib/common"
+ "github.com/ChainSafe/gossamer/lib/grandpa"
rtstorage "github.com/ChainSafe/gossamer/lib/runtime/storage"
- "github.com/ChainSafe/gossamer/lib/services"
"github.com/ChainSafe/gossamer/lib/transaction"
)
@@ -68,14 +68,6 @@ type TransactionState interface {
PendingInPool() []*transaction.ValidTransaction
}
-// FinalityGadget is the interface that a finality gadget must implement
-type FinalityGadget interface {
- services.Service
-
- UpdateAuthorities(ad []*types.Authority)
- Authorities() []*types.Authority
-}
-
// BlockProducer is the interface that a block production service must implement
type BlockProducer interface {
GetBlockChannel() <-chan types.Block
@@ -100,3 +92,11 @@ type EpochState interface {
SetCurrentEpoch(epoch uint64) error
GetCurrentEpoch() (uint64, error)
}
+
+// GrandpaState is the interface for the state.GrandpaState
+type GrandpaState interface {
+ SetNextChange(authorities []*grandpa.Voter, number *big.Int) error
+ IncrementSetID() error
+ SetNextPause(number *big.Int) error
+ SetNextResume(number *big.Int) error
+}
diff --git a/dot/core/service.go b/dot/core/service.go
index 93120e87c1..cf72bdfe65 100644
--- a/dot/core/service.go
+++ b/dot/core/service.go
@@ -58,10 +58,6 @@ type Service struct {
blockProducer BlockProducer
isBlockProducer bool
- // Finality gadget variables
- finalityGadget FinalityGadget
- isFinalityAuthority bool
-
// Block verification
verifier Verifier
@@ -81,19 +77,17 @@ type Service struct {
// Config holds the configuration for the core Service.
type Config struct {
- LogLvl log.Lvl
- BlockState BlockState
- EpochState EpochState
- StorageState StorageState
- TransactionState TransactionState
- Network Network
- Keystore *keystore.GlobalKeystore
- Runtime runtime.Instance
- BlockProducer BlockProducer
- IsBlockProducer bool
- FinalityGadget FinalityGadget
- IsFinalityAuthority bool
- Verifier Verifier
+ LogLvl log.Lvl
+ BlockState BlockState
+ EpochState EpochState
+ StorageState StorageState
+ TransactionState TransactionState
+ Network Network
+ Keystore *keystore.GlobalKeystore
+ Runtime runtime.Instance
+ BlockProducer BlockProducer
+ IsBlockProducer bool
+ Verifier Verifier
NewBlocks chan types.Block // only used for testing purposes
}
@@ -121,10 +115,6 @@ func NewService(cfg *Config) (*Service, error) {
return nil, ErrNilBlockProducer
}
- if cfg.IsFinalityAuthority && cfg.FinalityGadget == nil {
- return nil, ErrNilFinalityGadget
- }
-
h := log.StreamHandler(os.Stdout, log.TerminalFormat())
h = log.CallerFileHandler(h)
logger.SetHandler(log.LvlFilterHandler(cfg.LogLvl, h))
@@ -148,25 +138,23 @@ func NewService(cfg *Config) (*Service, error) {
ctx, cancel := context.WithCancel(context.Background())
srv := &Service{
- ctx: ctx,
- cancel: cancel,
- rt: cfg.Runtime,
- codeHash: codeHash,
- keys: cfg.Keystore,
- blkRec: cfg.NewBlocks,
- blockState: cfg.BlockState,
- epochState: cfg.EpochState,
- storageState: cfg.StorageState,
- transactionState: cfg.TransactionState,
- net: cfg.Network,
- isBlockProducer: cfg.IsBlockProducer,
- blockProducer: cfg.BlockProducer,
- finalityGadget: cfg.FinalityGadget,
- verifier: cfg.Verifier,
- isFinalityAuthority: cfg.IsFinalityAuthority,
- lock: &sync.Mutex{},
- blockAddCh: blockAddCh,
- blockAddChID: id,
+ ctx: ctx,
+ cancel: cancel,
+ rt: cfg.Runtime,
+ codeHash: codeHash,
+ keys: cfg.Keystore,
+ blkRec: cfg.NewBlocks,
+ blockState: cfg.BlockState,
+ epochState: cfg.EpochState,
+ storageState: cfg.StorageState,
+ transactionState: cfg.TransactionState,
+ net: cfg.Network,
+ isBlockProducer: cfg.IsBlockProducer,
+ blockProducer: cfg.BlockProducer,
+ verifier: cfg.Verifier,
+ lock: &sync.Mutex{},
+ blockAddCh: blockAddCh,
+ blockAddChID: id,
}
if cfg.NewBlocks != nil {
diff --git a/dot/core/test_helpers.go b/dot/core/test_helpers.go
index 548f778d74..5366d4cf8f 100644
--- a/dot/core/test_helpers.go
+++ b/dot/core/test_helpers.go
@@ -96,29 +96,6 @@ func (n *mockNetwork) SendMessage(m network.NotificationsMessage) {
n.Message = m
}
-// mockFinalityGadget implements the FinalityGadget interface
-type mockFinalityGadget struct {
- auths []*types.Authority
-}
-
-// Start mocks starting
-func (fg *mockFinalityGadget) Start() error {
- return nil
-}
-
-// Stop mocks stopping
-func (fg *mockFinalityGadget) Stop() error {
- return nil
-}
-
-func (fg *mockFinalityGadget) UpdateAuthorities(ad []*types.Authority) {
- fg.auths = ad
-}
-
-func (fg *mockFinalityGadget) Authorities() []*types.Authority {
- return fg.auths
-}
-
// NewTestService creates a new test core service
func NewTestService(t *testing.T, cfg *Config) *Service {
if cfg == nil {
diff --git a/dot/node.go b/dot/node.go
index 8c5df36f31..86cf10da4e 100644
--- a/dot/node.go
+++ b/dot/node.go
@@ -248,7 +248,6 @@ func NewNode(cfg *Config, ks *keystore.GlobalKeystore, stopFunc func()) (*Node,
return nil, err
}
nodeSrvcs = append(nodeSrvcs, fg)
- dh.SetFinalityGadget(fg) // TODO: this should be cleaned up
// Syncer
syncer, err := createSyncService(cfg, stateSrvc, bp, fg, dh, ver, rt)
@@ -259,7 +258,7 @@ func NewNode(cfg *Config, ks *keystore.GlobalKeystore, stopFunc func()) (*Node,
// Core Service
// create core service and append core service to node services
- coreSrvc, err := createCoreService(cfg, bp, fg, ver, rt, ks, stateSrvc, networkSrvc)
+ coreSrvc, err := createCoreService(cfg, bp, ver, rt, ks, stateSrvc, networkSrvc)
if err != nil {
return nil, fmt.Errorf("failed to create core service: %s", err)
}
diff --git a/dot/services.go b/dot/services.go
index 3f7d8ecbf9..256f703296 100644
--- a/dot/services.go
+++ b/dot/services.go
@@ -214,7 +214,7 @@ func createBABEService(cfg *Config, rt runtime.Instance, st *state.Service, ks k
// Core Service
// createCoreService creates the core service from the provided core configuration
-func createCoreService(cfg *Config, bp core.BlockProducer, fg core.FinalityGadget, verifier *babe.VerificationManager, rt runtime.Instance, ks *keystore.GlobalKeystore, stateSrvc *state.Service, net *network.Service) (*core.Service, error) {
+func createCoreService(cfg *Config, bp core.BlockProducer, verifier *babe.VerificationManager, rt runtime.Instance, ks *keystore.GlobalKeystore, stateSrvc *state.Service, net *network.Service) (*core.Service, error) {
logger.Debug(
"creating core service...",
"authority", cfg.Core.Roles == types.AuthorityRole,
@@ -222,19 +222,17 @@ func createCoreService(cfg *Config, bp core.BlockProducer, fg core.FinalityGadge
// set core configuration
coreConfig := &core.Config{
- LogLvl: cfg.Log.CoreLvl,
- BlockState: stateSrvc.Block,
- EpochState: stateSrvc.Epoch,
- StorageState: stateSrvc.Storage,
- TransactionState: stateSrvc.Transaction,
- BlockProducer: bp,
- FinalityGadget: fg,
- Keystore: ks,
- Runtime: rt,
- IsBlockProducer: cfg.Core.BabeAuthority,
- IsFinalityAuthority: cfg.Core.GrandpaAuthority,
- Verifier: verifier,
- Network: net,
+ LogLvl: cfg.Log.CoreLvl,
+ BlockState: stateSrvc.Block,
+ EpochState: stateSrvc.Epoch,
+ StorageState: stateSrvc.Storage,
+ TransactionState: stateSrvc.Transaction,
+ BlockProducer: bp,
+ Keystore: ks,
+ Runtime: rt,
+ IsBlockProducer: cfg.Core.BabeAuthority,
+ Verifier: verifier,
+ Network: net,
}
// create new core service
@@ -348,7 +346,7 @@ func createGRANDPAService(cfg *Config, rt runtime.Instance, st *state.Service, d
return nil, ErrInvalidKeystoreType
}
- voters := grandpa.NewVotersFromAuthorities(ad)
+ voters := types.NewGrandpaVotersFromAuthorities(ad)
keys := ks.Keypairs()
if len(keys) == 0 && cfg.Core.GrandpaAuthority {
@@ -358,8 +356,8 @@ func createGRANDPAService(cfg *Config, rt runtime.Instance, st *state.Service, d
gsCfg := &grandpa.Config{
LogLvl: cfg.Log.FinalityGadgetLvl,
BlockState: st.Block,
+ GrandpaState: st.Grandpa,
DigestHandler: dh,
- SetID: 1,
Voters: voters,
Authority: cfg.Core.GrandpaAuthority,
Network: net,
@@ -398,5 +396,5 @@ func createSyncService(cfg *Config, st *state.Service, bp sync.BlockProducer, fg
}
func createDigestHandler(st *state.Service, bp core.BlockProducer, verifier *babe.VerificationManager) (*core.DigestHandler, error) {
- return core.NewDigestHandler(st.Block, st.Epoch, bp, nil, verifier)
+ return core.NewDigestHandler(st.Block, st.Epoch, st.Grandpa, bp, verifier)
}
diff --git a/dot/services_test.go b/dot/services_test.go
index e5338c7251..c29bb577e9 100644
--- a/dot/services_test.go
+++ b/dot/services_test.go
@@ -85,16 +85,8 @@ func TestCreateCoreService(t *testing.T) {
rt, err := createRuntime(cfg, stateSrvc, ks, networkSrvc)
require.NoError(t, err)
- dh, err := createDigestHandler(stateSrvc, nil, nil)
- require.NoError(t, err)
-
- gs, err := createGRANDPAService(cfg, rt, stateSrvc, dh, ks.Gran, networkSrvc)
- require.NoError(t, err)
-
- coreSrvc, err := createCoreService(cfg, nil, gs, nil, rt, ks, stateSrvc, networkSrvc)
+ coreSrvc, err := createCoreService(cfg, nil, nil, rt, ks, stateSrvc, networkSrvc)
require.Nil(t, err)
-
- // TODO: improve dot tests #687
require.NotNil(t, coreSrvc)
}
@@ -208,13 +200,7 @@ func TestCreateRPCService(t *testing.T) {
rt, err := createRuntime(cfg, stateSrvc, ks, networkSrvc)
require.NoError(t, err)
- dh, err := createDigestHandler(stateSrvc, nil, nil)
- require.NoError(t, err)
-
- gs, err := createGRANDPAService(cfg, rt, stateSrvc, dh, ks.Gran, networkSrvc)
- require.NoError(t, err)
-
- coreSrvc, err := createCoreService(cfg, nil, gs, nil, rt, ks, stateSrvc, networkSrvc)
+ coreSrvc, err := createCoreService(cfg, nil, nil, rt, ks, stateSrvc, networkSrvc)
require.Nil(t, err)
sysSrvc, err := createSystemService(&cfg.System, stateSrvc)
@@ -335,13 +321,7 @@ func TestNewWebSocketServer(t *testing.T) {
rt, err := createRuntime(cfg, stateSrvc, ks, networkSrvc)
require.NoError(t, err)
- dh, err := createDigestHandler(stateSrvc, nil, nil)
- require.NoError(t, err)
-
- gs, err := createGRANDPAService(cfg, rt, stateSrvc, dh, ks.Gran, networkSrvc)
- require.NoError(t, err)
-
- coreSrvc, err := createCoreService(cfg, nil, gs, nil, rt, ks, stateSrvc, networkSrvc)
+ coreSrvc, err := createCoreService(cfg, nil, nil, rt, ks, stateSrvc, networkSrvc)
require.Nil(t, err)
sysSrvc, err := createSystemService(&cfg.System, stateSrvc)
diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go
new file mode 100644
index 0000000000..9c8a9e8830
--- /dev/null
+++ b/dot/state/grandpa.go
@@ -0,0 +1,221 @@
+// Copyright 2019 ChainSafe Systems (ON) Corp.
+// This file is part of gossamer.
+//
+// The gossamer library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The gossamer library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the gossamer library. If not, see .
+
+package state
+
+import (
+ "bytes"
+ "encoding/binary"
+ "errors"
+ "math/big"
+
+ "github.com/ChainSafe/chaindb"
+ "github.com/ChainSafe/gossamer/dot/types"
+ "github.com/ChainSafe/gossamer/lib/scale"
+)
+
+var (
+ genesisSetID = uint64(0)
+ grandpaPrefix = "grandpa"
+ authoritiesPrefix = []byte("auth")
+ setIDChangePrefix = []byte("change")
+ pauseKey = []byte("pause")
+ resumeKey = []byte("resume")
+ currentSetIDKey = []byte("setID")
+)
+
+// GrandpaState tracks information related to grandpa
+type GrandpaState struct {
+ baseDB chaindb.Database
+ db chaindb.Database
+}
+
+// NewGrandpaStateFromGenesis returns a new GrandpaState given the grandpa genesis authorities
+func NewGrandpaStateFromGenesis(db chaindb.Database, genesisAuthorities []*types.GrandpaVoter) (*GrandpaState, error) {
+ grandpaDB := chaindb.NewTable(db, grandpaPrefix)
+ s := &GrandpaState{
+ baseDB: db,
+ db: grandpaDB,
+ }
+
+ err := s.setCurrentSetID(genesisSetID)
+ if err != nil {
+ return nil, err
+ }
+
+ err = s.setAuthorities(genesisSetID, genesisAuthorities)
+ if err != nil {
+ return nil, err
+ }
+
+ return s, nil
+}
+
+// NewGrandpaState returns a new GrandpaState
+func NewGrandpaState(db chaindb.Database) (*GrandpaState, error) {
+ return &GrandpaState{
+ baseDB: db,
+ db: chaindb.NewTable(db, grandpaPrefix),
+ }, nil
+}
+
+func authoritiesKey(setID uint64) []byte {
+ buf := make([]byte, 8)
+ binary.LittleEndian.PutUint64(buf, setID)
+ return append(authoritiesPrefix, buf...)
+}
+
+func setIDChangeKey(setID uint64) []byte {
+ buf := make([]byte, 8)
+ binary.LittleEndian.PutUint64(buf, setID)
+ return append(setIDChangePrefix, buf...)
+}
+
+// setAuthorities sets the authorities for a given setID
+func (s *GrandpaState) setAuthorities(setID uint64, authorities []*types.GrandpaVoter) error {
+ enc, err := scale.Encode(authorities)
+ if err != nil {
+ return err
+ }
+
+ return s.db.Put(authoritiesKey(setID), enc)
+}
+
+// GetAuthorities returns the authorities for the given setID
+func (s *GrandpaState) GetAuthorities(setID uint64) ([]*types.GrandpaVoter, error) {
+ enc, err := s.db.Get(authoritiesKey(setID))
+ if err != nil {
+ return nil, err
+ }
+
+ r := &bytes.Buffer{}
+ _, err = r.Write(enc)
+ if err != nil {
+ return nil, err
+ }
+
+ v, err := types.DecodeGrandpaVoters(r)
+ if err != nil {
+ return nil, err
+ }
+
+ return v, nil
+}
+
+// setCurrentSetID sets the current set ID
+func (s *GrandpaState) setCurrentSetID(setID uint64) error {
+ buf := make([]byte, 8)
+ binary.LittleEndian.PutUint64(buf, setID)
+ return s.db.Put(currentSetIDKey, buf)
+}
+
+// GetCurrentSetID retrieves the current set ID
+func (s *GrandpaState) GetCurrentSetID() (uint64, error) {
+ id, err := s.db.Get(currentSetIDKey)
+ if err != nil {
+ return 0, err
+ }
+
+ if len(id) < 8 {
+ return 0, errors.New("invalid setID")
+ }
+
+ return binary.LittleEndian.Uint64(id), nil
+}
+
+// SetNextChange sets the next authority change
+func (s *GrandpaState) SetNextChange(authorities []*types.GrandpaVoter, number *big.Int) error {
+ currSetID, err := s.GetCurrentSetID()
+ if err != nil {
+ return err
+ }
+
+ nextSetID := currSetID + 1
+ err = s.setAuthorities(nextSetID, authorities)
+ if err != nil {
+ return err
+ }
+
+ err = s.setSetIDChangeAtBlock(nextSetID, number)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// IncrementSetID increments the set ID
+func (s *GrandpaState) IncrementSetID() error {
+ currSetID, err := s.GetCurrentSetID()
+ if err != nil {
+ return err
+ }
+
+ nextSetID := currSetID + 1
+ return s.setCurrentSetID(nextSetID)
+}
+
+// setSetIDChangeAtBlock sets a set ID change at a certain block
+func (s *GrandpaState) setSetIDChangeAtBlock(setID uint64, number *big.Int) error {
+ return s.db.Put(setIDChangeKey(setID), number.Bytes())
+}
+
+// GetSetIDChange returs the block number where the set ID was updated
+func (s *GrandpaState) GetSetIDChange(setID uint64) (*big.Int, error) {
+ num, err := s.db.Get(setIDChangeKey(setID))
+ if err != nil {
+ return nil, err
+ }
+
+ return big.NewInt(0).SetBytes(num), nil
+}
+
+// SetNextPause sets the next grandpa pause at the given block number
+func (s *GrandpaState) SetNextPause(number *big.Int) error {
+ return s.db.Put(pauseKey, number.Bytes())
+}
+
+// GetNextPause returns the block number of the next grandpa pause, nil if there is no upcoming pause
+func (s *GrandpaState) GetNextPause() (*big.Int, error) {
+ num, err := s.db.Get(pauseKey)
+ if err == chaindb.ErrKeyNotFound {
+ return nil, nil
+ }
+
+ if err != nil {
+ return nil, err
+ }
+
+ return big.NewInt(0).SetBytes(num), nil
+}
+
+// SetNextResume sets the next grandpa resume at the given block number
+func (s *GrandpaState) SetNextResume(number *big.Int) error {
+ return s.db.Put(resumeKey, number.Bytes())
+}
+
+// GetNextResume returns the block number of the next grandpa resume, nil if there is no upcoming resume
+func (s *GrandpaState) GetNextResume() (*big.Int, error) {
+ num, err := s.db.Get(resumeKey)
+ if err == chaindb.ErrKeyNotFound {
+ return nil, nil
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ return big.NewInt(0).SetBytes(num), nil
+}
diff --git a/dot/state/grandpa_test.go b/dot/state/grandpa_test.go
new file mode 100644
index 0000000000..24638e4dfd
--- /dev/null
+++ b/dot/state/grandpa_test.go
@@ -0,0 +1,83 @@
+// Copyright 2019 ChainSafe Systems (ON) Corp.
+// This file is part of gossamer.
+//
+// The gossamer library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The gossamer library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the gossamer library. If not, see .
+
+package state
+
+import (
+ "math/big"
+ "testing"
+
+ "github.com/ChainSafe/gossamer/dot/types"
+ "github.com/ChainSafe/gossamer/lib/crypto/ed25519"
+ "github.com/ChainSafe/gossamer/lib/keystore"
+
+ "github.com/stretchr/testify/require"
+)
+
+var (
+ kr, _ = keystore.NewEd25519Keyring()
+ testAuths = []*types.GrandpaVoter{
+ {Key: kr.Alice().Public().(*ed25519.PublicKey), ID: 0},
+ }
+)
+
+func TestNewGrandpaStateFromGenesis(t *testing.T) {
+ db := NewInMemoryDB(t)
+ gs, err := NewGrandpaStateFromGenesis(db, testAuths)
+ require.NoError(t, err)
+
+ currSetID, err := gs.GetCurrentSetID()
+ require.NoError(t, err)
+ require.Equal(t, genesisSetID, currSetID)
+
+ auths, err := gs.GetAuthorities(currSetID)
+ require.NoError(t, err)
+ require.Equal(t, testAuths, auths)
+}
+
+func TestGrandpaState_SetNextChange(t *testing.T) {
+ db := NewInMemoryDB(t)
+ gs, err := NewGrandpaStateFromGenesis(db, testAuths)
+ require.NoError(t, err)
+
+ testAuths2 := []*types.GrandpaVoter{
+ {Key: kr.Bob().Public().(*ed25519.PublicKey), ID: 0},
+ }
+
+ err = gs.SetNextChange(testAuths2, big.NewInt(1))
+ require.NoError(t, err)
+
+ auths, err := gs.GetAuthorities(genesisSetID + 1)
+ require.NoError(t, err)
+ require.Equal(t, testAuths2, auths)
+
+ atBlock, err := gs.GetSetIDChange(genesisSetID + 1)
+ require.NoError(t, err)
+ require.Equal(t, big.NewInt(1), atBlock)
+}
+
+func TestGrandpaState_IncrementSetID(t *testing.T) {
+ db := NewInMemoryDB(t)
+ gs, err := NewGrandpaStateFromGenesis(db, testAuths)
+ require.NoError(t, err)
+
+ err = gs.IncrementSetID()
+ require.NoError(t, err)
+
+ setID, err := gs.GetCurrentSetID()
+ require.NoError(t, err)
+ require.Equal(t, genesisSetID+1, setID)
+}
diff --git a/dot/state/initialize.go b/dot/state/initialize.go
new file mode 100644
index 0000000000..8ed36fe2c5
--- /dev/null
+++ b/dot/state/initialize.go
@@ -0,0 +1,206 @@
+// Copyright 2019 ChainSafe Systems (ON) Corp.
+// This file is part of gossamer.
+//
+// The gossamer library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The gossamer library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the gossamer library. If not, see .
+
+package state
+
+import (
+ "bytes"
+ "fmt"
+ "path/filepath"
+
+ "github.com/ChainSafe/gossamer/dot/types"
+ "github.com/ChainSafe/gossamer/lib/blocktree"
+ "github.com/ChainSafe/gossamer/lib/genesis"
+ "github.com/ChainSafe/gossamer/lib/runtime"
+ rtstorage "github.com/ChainSafe/gossamer/lib/runtime/storage"
+ "github.com/ChainSafe/gossamer/lib/runtime/wasmer"
+ "github.com/ChainSafe/gossamer/lib/trie"
+
+ "github.com/ChainSafe/chaindb"
+)
+
+// Initialise initialises the genesis state of the DB using the given storage trie. The trie should be loaded with the genesis storage state.
+// This only needs to be called during genesis initialisation of the node; it is not called during normal startup.
+func (s *Service) Initialise(gen *genesis.Genesis, header *types.Header, t *trie.Trie) error {
+ var db chaindb.Database
+ cfg := &chaindb.Config{}
+
+ // check database type
+ if s.isMemDB {
+ cfg.InMemory = true
+ }
+
+ // get data directory from service
+ basepath, err := filepath.Abs(s.dbPath)
+ if err != nil {
+ return fmt.Errorf("failed to read basepath: %s", err)
+ }
+
+ cfg.DataDir = basepath
+
+ // initialise database using data directory
+ db, err = chaindb.NewBadgerDB(cfg)
+ if err != nil {
+ return fmt.Errorf("failed to create database: %s", err)
+ }
+
+ if err = db.ClearAll(); err != nil {
+ return fmt.Errorf("failed to clear database: %s", err)
+ }
+
+ if err = t.Store(chaindb.NewTable(db, storagePrefix)); err != nil {
+ return fmt.Errorf("failed to write genesis trie to database: %w", err)
+ }
+
+ rt, err := s.createGenesisRuntime(t, gen)
+ if err != nil {
+ return err
+ }
+
+ babeCfg, err := s.loadBabeConfigurationFromRuntime(rt)
+ if err != nil {
+ return err
+ }
+
+ // write initial genesis values to database
+ if err = s.storeInitialValues(db, gen.GenesisData(), header, t); err != nil {
+ return fmt.Errorf("failed to write genesis values to database: %s", err)
+ }
+
+ // create and store blockree from genesis block
+ bt := blocktree.NewBlockTreeFromRoot(header, db)
+ err = bt.Store()
+ if err != nil {
+ return fmt.Errorf("failed to write blocktree to database: %s", err)
+ }
+
+ // create block state from genesis block
+ blockState, err := NewBlockStateFromGenesis(db, header)
+ if err != nil {
+ return fmt.Errorf("failed to create block state from genesis: %s", err)
+ }
+
+ // create storage state from genesis trie
+ storageState, err := NewStorageState(db, blockState, t)
+ if err != nil {
+ return fmt.Errorf("failed to create storage state from trie: %s", err)
+ }
+
+ epochState, err := NewEpochStateFromGenesis(db, babeCfg)
+ if err != nil {
+ return fmt.Errorf("failed to create epoch state: %s", err)
+ }
+
+ grandpaAuths, err := loadGrandpaAuthorities(t)
+ if err != nil {
+ return fmt.Errorf("failed to load grandpa authorities: %w", err)
+ }
+
+ grandpaState, err := NewGrandpaStateFromGenesis(db, grandpaAuths)
+ if err != nil {
+ return fmt.Errorf("failed to create grandpa state: %s", err)
+ }
+
+ // check database type
+ if s.isMemDB {
+ // append memory database to state service
+ s.db = db
+
+ // append storage state and block state to state service
+ s.Storage = storageState
+ s.Block = blockState
+ s.Epoch = epochState
+ s.Grandpa = grandpaState
+ } else if err = db.Close(); err != nil {
+ return fmt.Errorf("failed to close database: %s", err)
+ }
+
+ logger.Info("state", "genesis hash", blockState.genesisHash)
+ return nil
+}
+
+func (s *Service) loadBabeConfigurationFromRuntime(r runtime.Instance) (*types.BabeConfiguration, error) {
+ // load and store initial BABE epoch configuration
+ babeCfg, err := r.BabeConfiguration()
+ if err != nil {
+ return nil, fmt.Errorf("failed to fetch genesis babe configuration: %w", err)
+ }
+
+ r.Stop()
+
+ if s.BabeThresholdDenominator != 0 {
+ babeCfg.C1 = s.BabeThresholdNumerator
+ babeCfg.C2 = s.BabeThresholdDenominator
+ }
+
+ return babeCfg, nil
+}
+
+func loadGrandpaAuthorities(t *trie.Trie) ([]*types.GrandpaVoter, error) {
+ authsRaw := t.Get(runtime.GrandpaAuthoritiesKey)
+ if authsRaw == nil {
+ return []*types.GrandpaVoter{}, nil
+ }
+
+ r := &bytes.Buffer{}
+ _, _ = r.Write(authsRaw[1:])
+ return types.DecodeGrandpaVoters(r)
+}
+
+// storeInitialValues writes initial genesis values to the state database
+func (s *Service) storeInitialValues(db chaindb.Database, data *genesis.Data, header *types.Header, t *trie.Trie) error {
+ // write genesis trie to database
+ if err := StoreTrie(chaindb.NewTable(db, storagePrefix), t); err != nil {
+ return fmt.Errorf("failed to write trie to database: %s", err)
+ }
+
+ // write storage hash to database
+ if err := StoreLatestStorageHash(db, t.MustHash()); err != nil {
+ return fmt.Errorf("failed to write storage hash to database: %s", err)
+ }
+
+ // write best block hash to state database
+ if err := StoreBestBlockHash(db, header.Hash()); err != nil {
+ return fmt.Errorf("failed to write best block hash to database: %s", err)
+ }
+
+ // write genesis data to state database
+ if err := StoreGenesisData(db, data); err != nil {
+ return fmt.Errorf("failed to write genesis data to database: %s", err)
+ }
+
+ return nil
+}
+
+func (s *Service) createGenesisRuntime(t *trie.Trie, gen *genesis.Genesis) (runtime.Instance, error) {
+ // load genesis state into database
+ genTrie, err := rtstorage.NewTrieState(t)
+ if err != nil {
+ return nil, fmt.Errorf("failed to instantiate TrieState: %w", err)
+ }
+
+ // create genesis runtime
+ rtCfg := &wasmer.Config{}
+ rtCfg.Storage = genTrie
+ rtCfg.LogLvl = s.logLvl
+
+ r, err := wasmer.NewRuntimeFromGenesis(gen, rtCfg)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create genesis runtime: %w", err)
+ }
+
+ return r, nil
+}
diff --git a/dot/state/service.go b/dot/state/service.go
index 3bc243e067..39be20ce74 100644
--- a/dot/state/service.go
+++ b/dot/state/service.go
@@ -25,9 +25,6 @@ import (
"github.com/ChainSafe/gossamer/dot/types"
"github.com/ChainSafe/gossamer/lib/blocktree"
- "github.com/ChainSafe/gossamer/lib/genesis"
- rtstorage "github.com/ChainSafe/gossamer/lib/runtime/storage"
- "github.com/ChainSafe/gossamer/lib/runtime/wasmer"
"github.com/ChainSafe/gossamer/lib/trie"
"github.com/ChainSafe/chaindb"
@@ -46,6 +43,7 @@ type Service struct {
Block *BlockState
Transaction *TransactionState
Epoch *EpochState
+ Grandpa *GrandpaState
closeCh chan interface{}
// Below are for testing only.
@@ -82,151 +80,9 @@ func (s *Service) DB() chaindb.Database {
return s.db
}
-// Initialise initialises the genesis state of the DB using the given storage trie. The trie should be loaded with the genesis storage state.
-// This only needs to be called during genesis initialisation of the node; it doesn't need to be called during normal startup.
-func (s *Service) Initialise(gen *genesis.Genesis, header *types.Header, t *trie.Trie) error {
- var db chaindb.Database
- cfg := &chaindb.Config{}
-
- // check database type
- if s.isMemDB {
- cfg.InMemory = true
- }
-
- // get data directory from service
- basepath, err := filepath.Abs(s.dbPath)
- if err != nil {
- return fmt.Errorf("failed to read basepath: %s", err)
- }
-
- cfg.DataDir = basepath
-
- // initialise database using data directory
- db, err = chaindb.NewBadgerDB(cfg)
- if err != nil {
- return fmt.Errorf("failed to create database: %s", err)
- }
-
- if err = db.ClearAll(); err != nil {
- return fmt.Errorf("failed to clear database: %s", err)
- }
-
- if err = t.Store(chaindb.NewTable(db, storagePrefix)); err != nil {
- return fmt.Errorf("failed to write genesis trie to database: %w", err)
- }
-
- babeCfg, err := s.loadBabeConfigurationFromRuntime(t, gen)
- if err != nil {
- return err
- }
-
- // write initial genesis values to database
- if err = s.storeInitialValues(db, gen.GenesisData(), header, t); err != nil {
- return fmt.Errorf("failed to write genesis values to database: %s", err)
- }
-
- // create and store blockree from genesis block
- bt := blocktree.NewBlockTreeFromRoot(header, db)
- err = bt.Store()
- if err != nil {
- return fmt.Errorf("failed to write blocktree to database: %s", err)
- }
-
- // create block state from genesis block
- blockState, err := NewBlockStateFromGenesis(db, header)
- if err != nil {
- return fmt.Errorf("failed to create block state from genesis: %s", err)
- }
-
- // create storage state from genesis trie
- storageState, err := NewStorageState(db, blockState, t)
- if err != nil {
- return fmt.Errorf("failed to create storage state from trie: %s", err)
- }
-
- epochState, err := NewEpochStateFromGenesis(db, babeCfg)
- if err != nil {
- return fmt.Errorf("failed to create epoch state: %s", err)
- }
-
- // check database type
- if s.isMemDB {
- // append memory database to state service
- s.db = db
-
- // append storage state and block state to state service
- s.Storage = storageState
- s.Block = blockState
- s.Epoch = epochState
- } else if err = db.Close(); err != nil {
- return fmt.Errorf("failed to close database: %s", err)
- }
-
- logger.Info("state", "genesis hash", blockState.genesisHash)
- return nil
-}
-
-func (s *Service) loadBabeConfigurationFromRuntime(t *trie.Trie, gen *genesis.Genesis) (*types.BabeConfiguration, error) {
- // load genesis state into database
- genTrie, err := rtstorage.NewTrieState(t)
- if err != nil {
- return nil, fmt.Errorf("failed to instantiate TrieState: %w", err)
- }
-
- // create genesis runtime
- rtCfg := &wasmer.Config{}
- rtCfg.Storage = genTrie
- rtCfg.LogLvl = s.logLvl
-
- r, err := wasmer.NewRuntimeFromGenesis(gen, rtCfg)
- if err != nil {
- return nil, fmt.Errorf("failed to create genesis runtime: %w", err)
- }
-
- // load and store initial BABE epoch configuration
- babeCfg, err := r.BabeConfiguration()
- if err != nil {
- return nil, fmt.Errorf("failed to fetch genesis babe configuration: %w", err)
- }
-
- r.Stop()
-
- if s.BabeThresholdDenominator != 0 {
- babeCfg.C1 = s.BabeThresholdNumerator
- babeCfg.C2 = s.BabeThresholdDenominator
- }
-
- return babeCfg, nil
-}
-
-// storeInitialValues writes initial genesis values to the state database
-func (s *Service) storeInitialValues(db chaindb.Database, data *genesis.Data, header *types.Header, t *trie.Trie) error {
- // write genesis trie to database
- if err := StoreTrie(chaindb.NewTable(db, storagePrefix), t); err != nil {
- return fmt.Errorf("failed to write trie to database: %s", err)
- }
-
- // write storage hash to database
- if err := StoreLatestStorageHash(db, t.MustHash()); err != nil {
- return fmt.Errorf("failed to write storage hash to database: %s", err)
- }
-
- // write best block hash to state database
- if err := StoreBestBlockHash(db, header.Hash()); err != nil {
- return fmt.Errorf("failed to write best block hash to database: %s", err)
- }
-
- // write genesis data to state database
- if err := StoreGenesisData(db, data); err != nil {
- return fmt.Errorf("failed to write genesis data to database: %s", err)
- }
-
- return nil
-}
-
// Start initialises the Storage database and the Block database.
func (s *Service) Start() error {
- if !s.isMemDB && (s.Storage != nil || s.Block != nil || s.Epoch != nil) {
+ if !s.isMemDB && (s.Storage != nil || s.Block != nil || s.Epoch != nil || s.Grandpa != nil) {
return nil
}
@@ -312,6 +168,11 @@ func (s *Service) Start() error {
return fmt.Errorf("failed to create epoch state: %w", err)
}
+ s.Grandpa, err = NewGrandpaState(db)
+ if err != nil {
+ return fmt.Errorf("failed to create grandpa state: %w", err)
+ }
+
num, _ := s.Block.BestBlockNumber()
logger.Info("created state service", "head", s.Block.BestBlockHash(), "highest number", num)
// Start background goroutine to GC pruned keys.
diff --git a/dot/types/grandpa.go b/dot/types/grandpa.go
index fa6dd627ac..65a9af80e2 100644
--- a/dot/types/grandpa.go
+++ b/dot/types/grandpa.go
@@ -1,10 +1,12 @@
package types
import (
+ "fmt"
"io"
"github.com/ChainSafe/gossamer/lib/common"
"github.com/ChainSafe/gossamer/lib/crypto/ed25519"
+ "github.com/ChainSafe/gossamer/lib/scale"
)
// GrandpaAuthoritiesRaw represents a GRANDPA authority where their key is a byte array
@@ -58,3 +60,108 @@ func GrandpaAuthoritiesRawToAuthorities(adr []*GrandpaAuthoritiesRaw) ([]*Author
return ad, nil
}
+
+// GrandpaVoter represents a GRANDPA voter
+type GrandpaVoter struct {
+ Key *ed25519.PublicKey
+ ID uint64
+}
+
+// PublicKeyBytes returns the voter key as PublicKeyBytes
+func (v *GrandpaVoter) PublicKeyBytes() ed25519.PublicKeyBytes {
+ return v.Key.AsBytes()
+}
+
+// String returns a formatted GrandpaVoter string
+func (v *GrandpaVoter) String() string {
+ return fmt.Sprintf("[key=0x%s id=%d]", v.PublicKeyBytes(), v.ID)
+}
+
+// Decode will decode the Reader into a GrandpaVoter
+func (v *GrandpaVoter) Decode(r io.Reader) error {
+ keyBytes, err := common.Read32Bytes(r)
+ if err != nil {
+ return err
+ }
+
+ key, err := ed25519.NewPublicKey(keyBytes[:])
+ if err != nil {
+ return err
+ }
+
+ id, err := common.ReadUint64(r)
+ if err != nil {
+ return err
+ }
+
+ v.Key = key
+ v.ID = id
+ return nil
+}
+
+// NewGrandpaVotersFromAuthorities returns an array of GrandpaVoters given an array of GrandpaAuthorities
+func NewGrandpaVotersFromAuthorities(ad []*Authority) []*GrandpaVoter {
+ v := make([]*GrandpaVoter, len(ad))
+
+ for i, d := range ad {
+ if pk, ok := d.Key.(*ed25519.PublicKey); ok {
+ v[i] = &GrandpaVoter{
+ Key: pk,
+ ID: d.Weight,
+ }
+ }
+ }
+
+ return v
+}
+
+// NewGrandpaVotersFromAuthoritiesRaw returns an array of GrandpaVoters given an array of GrandpaAuthoritiesRaw
+func NewGrandpaVotersFromAuthoritiesRaw(ad []*GrandpaAuthoritiesRaw) ([]*GrandpaVoter, error) {
+ v := make([]*GrandpaVoter, len(ad))
+
+ for i, d := range ad {
+ key, err := ed25519.NewPublicKey(d.Key[:])
+ if err != nil {
+ return nil, err
+ }
+
+ v[i] = &GrandpaVoter{
+ Key: key,
+ ID: d.ID,
+ }
+ }
+
+ return v, nil
+}
+
+// GrandpaVoters represents []*GrandpaVoter
+type GrandpaVoters []*GrandpaVoter
+
+// String returns a formatted Voters string
+func (v GrandpaVoters) String() string {
+ str := ""
+ for _, w := range v {
+ str = str + w.String() + " "
+ }
+ return str
+}
+
+// DecodeGrandpaVoters returns a SCALE decoded GrandpaVoters
+func DecodeGrandpaVoters(r io.Reader) (GrandpaVoters, error) {
+ sd := &scale.Decoder{Reader: r}
+ length, err := sd.DecodeInteger()
+ if err != nil {
+ return nil, err
+ }
+
+ voters := make([]*GrandpaVoter, length)
+ for i := range voters {
+ voters[i] = new(GrandpaVoter)
+ err = voters[i].Decode(r)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return voters, nil
+}
diff --git a/lib/grandpa/errors.go b/lib/grandpa/errors.go
index df529358d2..ff2df5fcc2 100644
--- a/lib/grandpa/errors.go
+++ b/lib/grandpa/errors.go
@@ -25,6 +25,9 @@ import (
// ErrNilBlockState is returned when BlockState is nil
var ErrNilBlockState = errors.New("cannot have nil BlockState")
+// ErrNilGrandpaState is returned when GrandpaState is nil
+var ErrNilGrandpaState = errors.New("cannot have nil GrandpaState")
+
// ErrNilDigestHandler is returned when DigestHandler is nil
var ErrNilDigestHandler = errors.New("cannot have nil DigestHandler")
diff --git a/lib/grandpa/grandpa.go b/lib/grandpa/grandpa.go
index 3fcfbafbb8..43181da1b5 100644
--- a/lib/grandpa/grandpa.go
+++ b/lib/grandpa/grandpa.go
@@ -35,7 +35,7 @@ import (
)
var (
- interval = time.Second
+ interval = time.Second // TODO: make this configurable; currently 1s is same as substrate; total round length is then 2s
logger = log.New("pkg", "grandpa")
)
@@ -45,6 +45,7 @@ type Service struct {
ctx context.Context
cancel context.CancelFunc
blockState BlockState
+ grandpaState GrandpaState
digestHandler DigestHandler
keypair *ed25519.Keypair // TODO: change to grandpa keystore
mapLock sync.Mutex
@@ -66,7 +67,6 @@ type Service struct {
pcEquivocations map[ed25519.PublicKeyBytes][]*Vote // equivocatory votes for current pre-commit stage
tracker *tracker // tracker of vote messages we may need in the future
head *types.Header // most recently finalised block
- nextAuthorities []*Voter // if not nil, the updated authorities for the next round
// historical information
preVotedBlock map[uint64]*Vote // map of round number -> pre-voted block
@@ -81,10 +81,10 @@ type Service struct {
type Config struct {
LogLvl log.Lvl
BlockState BlockState
+ GrandpaState GrandpaState
DigestHandler DigestHandler
Network Network
Voters []*Voter
- SetID uint64
Keypair *ed25519.Keypair
Authority bool
}
@@ -95,6 +95,10 @@ func NewService(cfg *Config) (*Service, error) {
return nil, ErrNilBlockState
}
+ if cfg.GrandpaState == nil {
+ return nil, ErrNilGrandpaState
+ }
+
if cfg.DigestHandler == nil {
return nil, ErrNilDigestHandler
}
@@ -124,13 +128,18 @@ func NewService(cfg *Config) (*Service, error) {
return nil, err
}
- ctx, cancel := context.WithCancel(context.Background())
+ setID, err := cfg.GrandpaState.GetCurrentSetID()
+ if err != nil {
+ return nil, err
+ }
+ ctx, cancel := context.WithCancel(context.Background())
s := &Service{
ctx: ctx,
cancel: cancel,
- state: NewState(cfg.Voters, cfg.SetID, 0), // TODO: determine current round
+ state: NewState(cfg.Voters, setID, 0), // TODO: determine current round
blockState: cfg.BlockState,
+ grandpaState: cfg.GrandpaState,
digestHandler: cfg.DigestHandler,
keypair: cfg.Keypair,
authority: cfg.Authority,
@@ -188,42 +197,40 @@ func (s *Service) Stop() error {
return nil
}
-// Authorities returns the current grandpa authorities
-func (s *Service) Authorities() []*types.Authority {
+// authorities returns the current grandpa authorities
+func (s *Service) authorities() []*types.Authority {
ad := make([]*types.Authority, len(s.state.voters))
for i, v := range s.state.voters {
ad[i] = &types.Authority{
- Key: v.key,
- Weight: v.id,
+ Key: v.Key,
+ Weight: v.ID,
}
}
return ad
}
-// UpdateAuthorities schedules an update to the grandpa voter set and increments the setID at the end of the current round
-func (s *Service) UpdateAuthorities(ad []*types.Authority) {
- v := make([]*Voter, len(ad))
- for i, a := range ad {
- if pk, ok := a.Key.(*ed25519.PublicKey); ok {
- v[i] = &Voter{
- key: pk,
- id: a.Weight,
- }
- }
+// updateAuthorities updates the grandpa voter set, increments the setID, and resets the round numbers
+func (s *Service) updateAuthorities() error {
+ currSetID, err := s.grandpaState.GetCurrentSetID()
+ if err != nil {
+ return err
}
- s.nextAuthorities = v
-}
+ // set ID hasn't changed, do nothing
+ if currSetID == s.state.setID {
+ return nil
+ }
-// updateAuthorities updates the grandpa voter set, increments the setID, and resets the round numbers
-func (s *Service) updateAuthorities() {
- if s.nextAuthorities != nil {
- s.state.voters = s.nextAuthorities
- s.state.setID++
- s.state.round = 0
- s.nextAuthorities = nil
+ nextAuthorities, err := s.grandpaState.GetAuthorities(currSetID)
+ if err != nil {
+ return err
}
+
+ s.state.voters = nextAuthorities
+ s.state.setID = currSetID
+ s.state.round = 1 // round resets to 1 after a set ID change
+ return nil
}
func (s *Service) publicKeyBytes() ed25519.PublicKeyBytes {
@@ -233,7 +240,10 @@ func (s *Service) publicKeyBytes() ed25519.PublicKeyBytes {
// initiate initates a GRANDPA round
func (s *Service) initiate() error {
// if there is an authority change, execute it
- s.updateAuthorities()
+ err := s.updateAuthorities()
+ if err != nil {
+ return err
+ }
if s.state.round == 0 {
s.chanLock.Lock()
@@ -253,7 +263,6 @@ func (s *Service) initiate() error {
}
if s.authority {
- var err error
s.prevotes = make(map[ed25519.PublicKeyBytes]*Vote)
s.precommits = make(map[ed25519.PublicKeyBytes]*Vote)
s.pcJustifications = make(map[common.Hash][]*SignedPrecommit)
@@ -386,7 +395,7 @@ func (s *Service) playGrandpaRound() error {
primary := s.derivePrimary()
// if primary, broadcast the best final candidate from the previous round
- if bytes.Equal(primary.key.Encode(), s.keypair.Public().Encode()) {
+ if bytes.Equal(primary.Key.Encode(), s.keypair.Public().Encode()) {
msg, err := s.newCommitMessage(s.head, s.state.round-1).ToConsensusMessage()
if err != nil {
logger.Error("failed to encode finalisation message", "error", err)
diff --git a/lib/grandpa/grandpa_test.go b/lib/grandpa/grandpa_test.go
index 652ca0e00e..6d79c53ce8 100644
--- a/lib/grandpa/grandpa_test.go
+++ b/lib/grandpa/grandpa_test.go
@@ -41,7 +41,10 @@ var testGenesisHeader = &types.Header{
StateRoot: trie.EmptyHash,
}
-var kr, _ = keystore.NewEd25519Keyring()
+var (
+ kr, _ = keystore.NewEd25519Keyring()
+ voters = newTestVoters()
+)
type mockDigestHandler struct{}
@@ -64,30 +67,34 @@ func newTestState(t *testing.T) *state.Service {
block, err := state.NewBlockStateFromGenesis(db, testGenesisHeader)
require.NoError(t, err)
+ grandpa, err := state.NewGrandpaStateFromGenesis(db, voters)
+ require.NoError(t, err)
+
return &state.Service{
- Block: block,
+ Block: block,
+ Grandpa: grandpa,
}
}
func newTestVoters() []*Voter {
- voters := []*Voter{}
+ vs := []*Voter{}
for i, k := range kr.Keys {
- voters = append(voters, &Voter{
- key: k.Public().(*ed25519.PublicKey),
- id: uint64(i),
+ vs = append(vs, &Voter{
+ Key: k.Public().(*ed25519.PublicKey),
+ ID: uint64(i),
})
}
- return voters
+ return vs
}
func newTestService(t *testing.T) (*Service, *state.Service) {
st := newTestState(t)
- voters := newTestVoters()
net := newTestNetwork(t)
cfg := &Config{
BlockState: st.Block,
+ GrandpaState: st.Grandpa,
DigestHandler: &mockDigestHandler{},
Voters: voters,
Keypair: kr.Alice().(*ed25519.Keypair),
@@ -97,31 +104,30 @@ func newTestService(t *testing.T) (*Service, *state.Service) {
gs, err := NewService(cfg)
require.NoError(t, err)
-
return gs, st
}
func TestUpdateAuthorities(t *testing.T) {
gs, _ := newTestService(t)
- gs.UpdateAuthorities([]*types.Authority{
- {Key: kr.Alice().Public().(*ed25519.PublicKey), Weight: 0},
- })
-
- err := gs.Start()
+ err := gs.updateAuthorities()
require.NoError(t, err)
+ require.Equal(t, uint64(0), gs.state.setID)
- time.Sleep(time.Second)
- require.Equal(t, uint64(1), gs.state.setID)
- require.Equal(t, []*Voter{
- {key: kr.Alice().Public().(*ed25519.PublicKey), id: 0},
- }, gs.state.voters)
+ next := []*Voter{
+ {Key: kr.Alice().Public().(*ed25519.PublicKey), ID: 0},
+ }
- gs.UpdateAuthorities([]*types.Authority{
- {Key: kr.Alice().Public().(*ed25519.PublicKey), Weight: 0},
- })
+ err = gs.grandpaState.(*state.GrandpaState).SetNextChange(next, big.NewInt(1))
+ require.NoError(t, err)
- err = gs.Stop()
+ err = gs.grandpaState.(*state.GrandpaState).IncrementSetID()
require.NoError(t, err)
+
+ err = gs.updateAuthorities()
+ require.NoError(t, err)
+
+ require.Equal(t, uint64(1), gs.state.setID)
+ require.Equal(t, next, gs.state.voters)
}
func TestGetDirectVotes(t *testing.T) {
diff --git a/lib/grandpa/message_handler.go b/lib/grandpa/message_handler.go
index af09b9e23b..be2344bf49 100644
--- a/lib/grandpa/message_handler.go
+++ b/lib/grandpa/message_handler.go
@@ -418,7 +418,7 @@ func (h *MessageHandler) verifyJustification(just *SignedPrecommit, round, setID
// verify authority in justification set
authFound := false
- for _, auth := range h.grandpa.Authorities() {
+ for _, auth := range h.grandpa.authorities() {
justKey, err := just.AuthorityID.Encode()
if err != nil {
return err
diff --git a/lib/grandpa/round_test.go b/lib/grandpa/round_test.go
index 63cb801515..d33d6b32e9 100644
--- a/lib/grandpa/round_test.go
+++ b/lib/grandpa/round_test.go
@@ -97,11 +97,11 @@ func onSameChain(blockState BlockState, a, b common.Hash) bool {
func setupGrandpa(t *testing.T, kp *ed25519.Keypair) (*Service, chan GrandpaMessage, chan GrandpaMessage, chan GrandpaMessage) {
st := newTestState(t)
- voters := newTestVoters()
net := newTestNetwork(t)
cfg := &Config{
BlockState: st.Block,
+ GrandpaState: st.Grandpa,
DigestHandler: &mockDigestHandler{},
Voters: voters,
Keypair: kp,
diff --git a/lib/grandpa/state.go b/lib/grandpa/state.go
index 32e8745dca..f3d88fc18d 100644
--- a/lib/grandpa/state.go
+++ b/lib/grandpa/state.go
@@ -53,8 +53,14 @@ type BlockState interface {
BestBlockNumber() (*big.Int, error)
}
+// GrandpaState is the interface required by grandpa into the grandpa state
+type GrandpaState interface { //nolint
+ GetCurrentSetID() (uint64, error)
+ GetAuthorities(setID uint64) ([]*types.GrandpaVoter, error)
+}
+
// DigestHandler is the interface required by GRANDPA for the digest handler
-type DigestHandler interface {
+type DigestHandler interface { // TODO: remove, use GrandpaState
NextGrandpaAuthorityChange() uint64
}
diff --git a/lib/grandpa/types.go b/lib/grandpa/types.go
index cb61e5fff7..be3798658b 100644
--- a/lib/grandpa/types.go
+++ b/lib/grandpa/types.go
@@ -28,6 +28,12 @@ import (
"github.com/ChainSafe/gossamer/lib/scale"
)
+//nolint
+type (
+ Voter = types.GrandpaVoter
+ Voters = types.GrandpaVoters
+)
+
type subround byte
var (
@@ -65,50 +71,6 @@ func (s subround) String() string {
return "unknown"
}
-// Voter represents a GRANDPA voter
-type Voter struct {
- key *ed25519.PublicKey
- id uint64 //nolint:unused
-}
-
-// PublicKeyBytes returns the voter key as PublicKeyBytes
-func (v *Voter) PublicKeyBytes() ed25519.PublicKeyBytes {
- return v.key.AsBytes()
-}
-
-// String returns a formatted Voter string
-func (v *Voter) String() string {
- return fmt.Sprintf("[key=0x%s id=%d]", v.PublicKeyBytes(), v.id)
-}
-
-// NewVotersFromAuthorities returns an array of Voters given an array of GrandpaAuthorities
-func NewVotersFromAuthorities(ad []*types.Authority) []*Voter {
- v := make([]*Voter, len(ad))
-
- for i, d := range ad {
- if pk, ok := d.Key.(*ed25519.PublicKey); ok {
- v[i] = &Voter{
- key: pk,
- id: d.Weight,
- }
- }
- }
-
- return v
-}
-
-// Voters represents []*Voter
-type Voters []*Voter
-
-// String returns a formatted Voters string
-func (v Voters) String() string {
- str := ""
- for _, w := range v {
- str = str + w.String() + " "
- }
- return str
-}
-
// State represents a GRANDPA state
type State struct {
voters []*Voter // set of voters
@@ -131,7 +93,7 @@ func (s *State) pubkeyToVoter(pk *ed25519.PublicKey) (*Voter, error) {
id := max
for i, v := range s.voters {
- if bytes.Equal(pk.Encode(), v.key.Encode()) {
+ if bytes.Equal(pk.Encode(), v.Key.Encode()) {
id = uint64(i)
break
}
@@ -142,8 +104,8 @@ func (s *State) pubkeyToVoter(pk *ed25519.PublicKey) (*Voter, error) {
}
return &Voter{
- key: pk,
- id: id,
+ Key: pk,
+ ID: id,
}, nil
}
diff --git a/lib/grandpa/types_test.go b/lib/grandpa/types_test.go
index 7ee29885ec..c88d68b866 100644
--- a/lib/grandpa/types_test.go
+++ b/lib/grandpa/types_test.go
@@ -29,7 +29,6 @@ import (
)
func TestPubkeyToVoter(t *testing.T) {
- voters := newTestVoters()
kr, err := keystore.NewEd25519Keyring()
require.NoError(t, err)
diff --git a/lib/grandpa/vote_message.go b/lib/grandpa/vote_message.go
index 71fd6a2c5c..95d862dfdc 100644
--- a/lib/grandpa/vote_message.go
+++ b/lib/grandpa/vote_message.go
@@ -215,7 +215,7 @@ func (s *Service) validateMessage(m *VoteMessage) (*Vote, error) {
// it returns true if so, false otherwise.
// additionally, if the vote is equivocatory, it updates the service's votes and equivocations.
func (s *Service) checkForEquivocation(voter *Voter, vote *Vote, stage subround) bool {
- v := voter.key.AsBytes()
+ v := voter.Key.AsBytes()
var eq map[ed25519.PublicKeyBytes][]*Vote
var votes map[ed25519.PublicKeyBytes]*Vote
diff --git a/lib/grandpa/vote_message_test.go b/lib/grandpa/vote_message_test.go
index 8af3b1aed9..c5e4d9f75b 100644
--- a/lib/grandpa/vote_message_test.go
+++ b/lib/grandpa/vote_message_test.go
@@ -30,7 +30,6 @@ import (
func TestCheckForEquivocation_NoEquivocation(t *testing.T) {
st := newTestState(t)
- voters := newTestVoters()
net := newTestNetwork(t)
kr, err := keystore.NewEd25519Keyring()
@@ -38,6 +37,7 @@ func TestCheckForEquivocation_NoEquivocation(t *testing.T) {
cfg := &Config{
BlockState: st.Block,
+ GrandpaState: st.Grandpa,
DigestHandler: &mockDigestHandler{},
Voters: voters,
Keypair: kr.Bob().(*ed25519.Keypair),
@@ -62,7 +62,6 @@ func TestCheckForEquivocation_NoEquivocation(t *testing.T) {
func TestCheckForEquivocation_WithEquivocation(t *testing.T) {
st := newTestState(t)
- voters := newTestVoters()
net := newTestNetwork(t)
kr, err := keystore.NewEd25519Keyring()
@@ -70,6 +69,7 @@ func TestCheckForEquivocation_WithEquivocation(t *testing.T) {
cfg := &Config{
BlockState: st.Block,
+ GrandpaState: st.Grandpa,
DigestHandler: &mockDigestHandler{},
Voters: voters,
Keypair: kr.Bob().(*ed25519.Keypair),
@@ -89,7 +89,7 @@ func TestCheckForEquivocation_WithEquivocation(t *testing.T) {
voter := voters[0]
- gs.prevotes[voter.key.AsBytes()] = vote1
+ gs.prevotes[voter.Key.AsBytes()] = vote1
vote2, err := NewVoteFromHash(leaves[1], st.Block)
require.NoError(t, err)
@@ -99,12 +99,11 @@ func TestCheckForEquivocation_WithEquivocation(t *testing.T) {
require.Equal(t, 0, len(gs.prevotes))
require.Equal(t, 1, len(gs.pvEquivocations))
- require.Equal(t, 2, len(gs.pvEquivocations[voter.key.AsBytes()]))
+ require.Equal(t, 2, len(gs.pvEquivocations[voter.Key.AsBytes()]))
}
func TestCheckForEquivocation_WithExistingEquivocation(t *testing.T) {
st := newTestState(t)
- voters := newTestVoters()
net := newTestNetwork(t)
kr, err := keystore.NewEd25519Keyring()
@@ -112,6 +111,7 @@ func TestCheckForEquivocation_WithExistingEquivocation(t *testing.T) {
cfg := &Config{
BlockState: st.Block,
+ GrandpaState: st.Grandpa,
DigestHandler: &mockDigestHandler{},
Voters: voters,
Keypair: kr.Bob().(*ed25519.Keypair),
@@ -137,7 +137,7 @@ func TestCheckForEquivocation_WithExistingEquivocation(t *testing.T) {
voter := voters[0]
- gs.prevotes[voter.key.AsBytes()] = vote
+ gs.prevotes[voter.Key.AsBytes()] = vote
vote2 := NewVoteFromHeader(branches[0])
require.NoError(t, err)
@@ -156,12 +156,11 @@ func TestCheckForEquivocation_WithExistingEquivocation(t *testing.T) {
require.Equal(t, 0, len(gs.prevotes))
require.Equal(t, 1, len(gs.pvEquivocations))
- require.Equal(t, 3, len(gs.pvEquivocations[voter.key.AsBytes()]))
+ require.Equal(t, 3, len(gs.pvEquivocations[voter.Key.AsBytes()]))
}
func TestValidateMessage_Valid(t *testing.T) {
st := newTestState(t)
- voters := newTestVoters()
net := newTestNetwork(t)
kr, err := keystore.NewEd25519Keyring()
@@ -169,6 +168,7 @@ func TestValidateMessage_Valid(t *testing.T) {
cfg := &Config{
BlockState: st.Block,
+ GrandpaState: st.Grandpa,
DigestHandler: &mockDigestHandler{},
Voters: voters,
Keypair: kr.Bob().(*ed25519.Keypair),
@@ -192,7 +192,6 @@ func TestValidateMessage_Valid(t *testing.T) {
func TestValidateMessage_InvalidSignature(t *testing.T) {
st := newTestState(t)
- voters := newTestVoters()
net := newTestNetwork(t)
kr, err := keystore.NewEd25519Keyring()
@@ -200,6 +199,7 @@ func TestValidateMessage_InvalidSignature(t *testing.T) {
cfg := &Config{
BlockState: st.Block,
+ GrandpaState: st.Grandpa,
DigestHandler: &mockDigestHandler{},
Voters: voters,
Keypair: kr.Bob().(*ed25519.Keypair),
@@ -231,6 +231,7 @@ func TestValidateMessage_SetIDMismatch(t *testing.T) {
cfg := &Config{
BlockState: st.Block,
+ GrandpaState: st.Grandpa,
DigestHandler: &mockDigestHandler{},
Keypair: kr.Bob().(*ed25519.Keypair),
Network: net,
@@ -254,7 +255,6 @@ func TestValidateMessage_SetIDMismatch(t *testing.T) {
func TestValidateMessage_Equivocation(t *testing.T) {
st := newTestState(t)
- voters := newTestVoters()
net := newTestNetwork(t)
kr, err := keystore.NewEd25519Keyring()
@@ -262,6 +262,7 @@ func TestValidateMessage_Equivocation(t *testing.T) {
cfg := &Config{
BlockState: st.Block,
+ GrandpaState: st.Grandpa,
DigestHandler: &mockDigestHandler{},
Voters: voters,
Keypair: kr.Bob().(*ed25519.Keypair),
@@ -287,7 +288,7 @@ func TestValidateMessage_Equivocation(t *testing.T) {
voter := voters[0]
- gs.prevotes[voter.key.AsBytes()] = vote
+ gs.prevotes[voter.Key.AsBytes()] = vote
msg, err := gs.createVoteMessage(NewVoteFromHeader(branches[0]), prevote, kr.Alice())
require.NoError(t, err)
@@ -298,7 +299,6 @@ func TestValidateMessage_Equivocation(t *testing.T) {
func TestValidateMessage_BlockDoesNotExist(t *testing.T) {
st := newTestState(t)
- voters := newTestVoters()
net := newTestNetwork(t)
kr, err := keystore.NewEd25519Keyring()
@@ -306,6 +306,7 @@ func TestValidateMessage_BlockDoesNotExist(t *testing.T) {
cfg := &Config{
BlockState: st.Block,
+ GrandpaState: st.Grandpa,
DigestHandler: &mockDigestHandler{},
Voters: voters,
Keypair: kr.Bob().(*ed25519.Keypair),
@@ -331,7 +332,6 @@ func TestValidateMessage_BlockDoesNotExist(t *testing.T) {
func TestValidateMessage_IsNotDescendant(t *testing.T) {
st := newTestState(t)
- voters := newTestVoters()
net := newTestNetwork(t)
kr, err := keystore.NewEd25519Keyring()
@@ -339,6 +339,7 @@ func TestValidateMessage_IsNotDescendant(t *testing.T) {
cfg := &Config{
BlockState: st.Block,
+ GrandpaState: st.Grandpa,
DigestHandler: &mockDigestHandler{},
Voters: voters,
Keypair: kr.Bob().(*ed25519.Keypair),
diff --git a/lib/scale/encode.go b/lib/scale/encode.go
index fae886bc56..fd54ef0332 100644
--- a/lib/scale/encode.go
+++ b/lib/scale/encode.go
@@ -100,16 +100,24 @@ func (se *Encoder) EncodeCustom(in interface{}) (int, error) {
someType := reflect.TypeOf(in)
// TODO: if not a pointer, check if type pointer has Encode method
_, ok := someType.MethodByName("Encode")
- if ok {
- res := reflect.ValueOf(in).MethodByName("Encode").Call([]reflect.Value{})
- val := res[0].Interface()
- err := res[1].Interface()
- if err != nil {
- return 0, err.(error)
- }
+ if !ok {
+ return 0, fmt.Errorf("cannot call EncodeCustom")
+ }
+
+ res := reflect.ValueOf(in).MethodByName("Encode").Call([]reflect.Value{})
+ if len(res) == 0 {
+ return 0, fmt.Errorf("method Encode does not have any return values")
+ }
+ val := res[0].Interface()
+ if len(res) < 2 {
return se.Writer.Write(val.([]byte))
}
- return 0, fmt.Errorf("cannot call EncodeCustom")
+
+ err := res[1].Interface()
+ if err != nil {
+ return 0, err.(error)
+ }
+ return se.Writer.Write(val.([]byte))
}
// encodeCustomOrEncode tries to use EncodeCustom, if that fails, it reverts to Encode