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