From 698cffa6e4c732ad1155d1560df7341b6ee61a7e Mon Sep 17 00:00:00 2001 From: noot Date: Tue, 20 Apr 2021 14:54:33 -0400 Subject: [PATCH 01/23] pull changes --- lib/grandpa/errors.go | 7 +- lib/grandpa/grandpa.go | 4 +- lib/grandpa/message.go | 153 +++++++++++++++++++++++----- lib/grandpa/message_handler.go | 47 +++++---- lib/grandpa/message_handler_test.go | 38 +++---- lib/grandpa/message_test.go | 18 ++-- lib/grandpa/network_test.go | 2 +- lib/grandpa/round_test.go | 61 ++++++----- lib/grandpa/types.go | 7 +- lib/grandpa/vote_message.go | 16 +-- 10 files changed, 242 insertions(+), 111 deletions(-) diff --git a/lib/grandpa/errors.go b/lib/grandpa/errors.go index 34902002ac..73042e6b32 100644 --- a/lib/grandpa/errors.go +++ b/lib/grandpa/errors.go @@ -69,8 +69,8 @@ var ErrCannotDecodeSubround = errors.New("cannot decode invalid subround value") // ErrInvalidMessageType is returned when a network.Message cannot be decoded var ErrInvalidMessageType = errors.New("cannot decode invalid message type") -// ErrNotFinalizationMessage is returned when calling GetFinalizedHash on a message that isn't a FinalizationMessage -var ErrNotFinalizationMessage = errors.New("cannot get finalized hash from VoteMessage") +// ErrNotCommitMessage is returned when calling GetFinalizedHash on a message that isn't a CommitMessage +var ErrNotCommitMessage = errors.New("cannot get finalized hash from VoteMessage") // ErrNoJustification is returned when no justification can be found for a block, ie. it has not been finalized var ErrNoJustification = errors.New("no justification found for block") @@ -92,3 +92,6 @@ var ErrCatchUpResponseNotCompletable = errors.New("catch up response is not comp // ErrServicePaused is returned if the service is paused and waiting for catch up messages var ErrServicePaused = errors.New("service is paused") + +// ErrPrecommitSignatureMismatch is returned when the number of precommits and signatures in a CommitMessage do not match +var ErrPrecommitSignatureMismatch = errors.New("number of precommits does not match number of signatures") diff --git a/lib/grandpa/grandpa.go b/lib/grandpa/grandpa.go index bb4feabc73..75074f69e6 100644 --- a/lib/grandpa/grandpa.go +++ b/lib/grandpa/grandpa.go @@ -387,7 +387,7 @@ func (s *Service) playGrandpaRound() error { // if primary, broadcast the best final candidate from the previous round if bytes.Equal(primary.key.Encode(), s.keypair.Public().Encode()) { - msg, err := s.newFinalizationMessage(s.head, s.state.round-1).ToConsensusMessage() + msg, err := s.newCommitMessage(s.head, s.state.round-1).ToConsensusMessage() if err != nil { logger.Error("failed to encode finalization message", "error", err) } else { @@ -584,7 +584,7 @@ func (s *Service) attemptToFinalize() error { votes := s.getDirectVotes(precommit) logger.Debug("finalized block!!!", "setID", s.state.setID, "round", s.state.round, "hash", s.head.Hash(), "precommits #", pc, "votes for bfc #", votes[*bfc], "total votes for bfc", pc, "precommits", s.precommits) - msg, err := s.newFinalizationMessage(s.head, s.state.round).ToConsensusMessage() + msg, err := s.newCommitMessage(s.head, s.state.round).ToConsensusMessage() if err != nil { return err } diff --git a/lib/grandpa/message.go b/lib/grandpa/message.go index 0b76d4603d..112e29e2cf 100644 --- a/lib/grandpa/message.go +++ b/lib/grandpa/message.go @@ -19,6 +19,8 @@ package grandpa import ( "bytes" "fmt" + "io" + "math/big" "github.com/ChainSafe/gossamer/dot/network" "github.com/ChainSafe/gossamer/dot/types" @@ -36,11 +38,10 @@ type GrandpaMessage interface { //nolint var ( voteType byte = 0 - precommitType byte = 1 // TODO: precommitType is now part of voteType + commitType byte = 1 neighbourType byte = 2 catchUpRequestType byte = 3 catchUpResponseType byte = 4 - finalizationType byte = 5 // TODO: this is actually 1 ) // FullVote represents a vote with additional information about the state @@ -54,6 +55,7 @@ type FullVote struct { // SignedMessage represents a block hash and number signed by an authority type SignedMessage struct { + Stage subround // // 0 for pre-vote, 1 for pre-commit, 2 for primary proposal Hash common.Hash Number uint32 Signature [64]byte // ed25519.SignatureLength @@ -70,13 +72,12 @@ func (m *SignedMessage) String() string { type VoteMessage struct { Round uint64 SetID uint64 - Stage subround // 0 for pre-vote, 1 for pre-commit Message *SignedMessage } -// Type returns voteType or precommitType +// Type returns voteType func (v *VoteMessage) Type() byte { - return byte(v.Stage) + return voteType } // ToConsensusMessage converts the VoteMessage into a network-level consensus message @@ -86,9 +87,8 @@ func (v *VoteMessage) ToConsensusMessage() (*ConsensusMessage, error) { return nil, err } - typ := byte(v.Stage) return &ConsensusMessage{ - Data: append([]byte{typ}, enc...), + Data: append([]byte{voteType}, enc...), }, nil } @@ -117,38 +117,141 @@ func (m *NeighbourMessage) Type() byte { return neighbourType } -// FinalizationMessage represents a network finalization message -type FinalizationMessage struct { - Round uint64 - Vote *Vote - Justification []*SignedPrecommit +// AuthData represents signature data within a CommitMessage to be paired with a Precommit +type AuthData struct { + Signature [64]byte + AuthorityID ed25519.PublicKeyBytes +} + +// Encode SCALE encodes the AuthData +func (d *AuthData) Encode() ([]byte, error) { + return append(d.Signature[:], d.AuthorityID[:]...), nil +} + +// Decode SCALE decodes the data into an AuthData +func (d *AuthData) Decode(r io.Reader) error { + sig, err := common.Read64Bytes(r) + if err != nil { + return err + } + + copy(d.Signature[:], sig[:]) + + id, err := common.Read32Bytes(r) + if err != nil { + return err + } + + copy(d.AuthorityID[:], id[:]) + return nil +} + +// CommitMessage represents a network finalization message +type CommitMessage struct { + Round uint64 + SetID uint64 + Vote *Vote + Precommits []*Vote + AuthData []*AuthData +} + +// Decode SCALE decodes the data into a CommitMessage +func (f *CommitMessage) Decode(r io.Reader) (err error) { + f.Round, err = common.ReadUint64(r) + if err != nil { + return err + } + + f.SetID, err = common.ReadUint64(r) + if err != nil { + return err + } + + f.Vote, err = new(Vote).Decode(r) + if err != nil { + return err + } + + sd := &scale.Decoder{Reader: r} + numPrecommits, err := sd.Decode(new(big.Int)) + if err != nil { + return err + } + + f.Precommits = make([]*Vote, numPrecommits.(*big.Int).Int64()) + for i := range f.Precommits { + f.Precommits[i], err = new(Vote).Decode(r) + if err != nil { + return err + } + } + + numAuthData, err := sd.Decode(new(big.Int)) + if err != nil { + return err + } + fmt.Println(numAuthData) + + if numAuthData.(*big.Int).Cmp(numPrecommits.(*big.Int)) != 0 { + return ErrPrecommitSignatureMismatch + } + + f.AuthData = make([]*AuthData, numAuthData.(*big.Int).Int64()) + fmt.Println(f.AuthData) + for i := range f.AuthData { + f.AuthData[i] = new(AuthData) + err = f.AuthData[i].Decode(r) + if err != nil { + return err + } + fmt.Println(f.AuthData[i]) + } + return nil } -// Type returns finalizationType -func (f *FinalizationMessage) Type() byte { - return finalizationType +// Type returns commitType +func (f *CommitMessage) Type() byte { + return commitType } -// ToConsensusMessage converts the FinalizationMessage into a network-level consensus message -func (f *FinalizationMessage) ToConsensusMessage() (*ConsensusMessage, error) { +// ToConsensusMessage converts the CommitMessage into a network-level consensus message +func (f *CommitMessage) ToConsensusMessage() (*ConsensusMessage, error) { enc, err := scale.Encode(f) if err != nil { return nil, err } return &ConsensusMessage{ - Data: append([]byte{finalizationType}, enc...), + Data: append([]byte{commitType}, enc...), }, nil } -func (s *Service) newFinalizationMessage(header *types.Header, round uint64) *FinalizationMessage { - return &FinalizationMessage{ - Round: round, - Vote: NewVoteFromHeader(header), - Justification: s.justification[round], +func (s *Service) newCommitMessage(header *types.Header, round uint64) *CommitMessage { + just := s.justification[round] + precommits, authData := justificationToCompact(just) + return &CommitMessage{ + Round: round, + Vote: NewVoteFromHeader(header), + Precommits: precommits, + AuthData: authData, } } +func justificationToCompact(just []*SignedPrecommit) ([]*Vote, []*AuthData) { + precommits := make([]*Vote, len(just)) + authData := make([]*AuthData, len(just)) + + for i, j := range just { + precommits[i] = j.Vote + authData[i] = &AuthData{ + Signature: j.Signature, + AuthorityID: j.AuthorityID, + } + } + + return precommits, authData +} + type catchUpRequest struct { Round uint64 SetID uint64 @@ -179,8 +282,8 @@ func (r *catchUpRequest) ToConsensusMessage() (*ConsensusMessage, error) { } type catchUpResponse struct { - Round uint64 SetID uint64 + Round uint64 PreVoteJustification []*SignedPrecommit PreCommitJustification []*SignedPrecommit Hash common.Hash @@ -227,8 +330,8 @@ func (s *Service) newCatchUpResponse(round, setID uint64) (*catchUpResponse, err pcj := d.([]*SignedPrecommit) return &catchUpResponse{ - Round: round, SetID: setID, + Round: round, PreVoteJustification: pvj, PreCommitJustification: pcj, Hash: header.Hash(), diff --git a/lib/grandpa/message_handler.go b/lib/grandpa/message_handler.go index 2d31704b56..d5c4faaa51 100644 --- a/lib/grandpa/message_handler.go +++ b/lib/grandpa/message_handler.go @@ -18,6 +18,7 @@ package grandpa import ( "bytes" + "fmt" "math/big" "reflect" "sync" @@ -47,7 +48,7 @@ func NewMessageHandler(grandpa *Service, blockState BlockState) *MessageHandler } // HandleMessage handles a GRANDPA consensus message -// if it is a FinalizationMessage, it updates the BlockState +// if it is a CommitMessage, it updates the BlockState // if it is a VoteMessage, it sends it to the GRANDPA service func (h *MessageHandler) handleMessage(from peer.ID, msg *ConsensusMessage) (network.NotificationsMessage, error) { if msg == nil || len(msg.Data) == 0 { @@ -63,15 +64,15 @@ func (h *MessageHandler) handleMessage(from peer.ID, msg *ConsensusMessage) (net logger.Debug("handling grandpa message", "msg", m) switch m.Type() { - case voteType, precommitType: + case voteType: vm, ok := m.(*VoteMessage) if h.grandpa != nil && ok { // send vote message to grandpa service h.grandpa.in <- vm } - case finalizationType: - if fm, ok := m.(*FinalizationMessage); ok { - return h.handleFinalizationMessage(fm) + case commitType: + if fm, ok := m.(*CommitMessage); ok { + return h.handleCommitMessage(fm) } case neighbourType: nm, ok := m.(*NeighbourMessage) @@ -142,7 +143,7 @@ func (h *MessageHandler) handleNeighbourMessage(from peer.ID, msg *NeighbourMess return nil } -func (h *MessageHandler) handleFinalizationMessage(msg *FinalizationMessage) (*ConsensusMessage, error) { +func (h *MessageHandler) handleCommitMessage(msg *CommitMessage) (*ConsensusMessage, error) { logger.Debug("received finalization message", "round", msg.Round, "hash", msg.Vote.hash) if has, _ := h.blockState.HasFinalizedBlock(msg.Round, h.grandpa.state.setID); has { @@ -150,7 +151,7 @@ func (h *MessageHandler) handleFinalizationMessage(msg *FinalizationMessage) (*C } // check justification here - err := h.verifyFinalizationMessageJustification(msg) + err := h.verifyCommitMessageJustification(msg) if err != nil { return nil, err } @@ -168,7 +169,7 @@ func (h *MessageHandler) handleFinalizationMessage(msg *FinalizationMessage) (*C } // check if msg has same setID but is 2 or more rounds ahead of us, if so, return catch-up request to send - if msg.Round > h.grandpa.state.round+1 && !h.grandpa.paused.Load().(bool) { // TODO: FinalizationMessage does not have setID, confirm this is correct + if msg.Round > h.grandpa.state.round+1 && !h.grandpa.paused.Load().(bool) { // TODO: CommitMessage does not have setID, confirm this is correct h.grandpa.paused.Store(true) h.grandpa.state.round = msg.Round + 1 req := newCatchUpRequest(msg.Round, h.grandpa.state.setID) @@ -266,7 +267,7 @@ func (h *MessageHandler) verifyCatchUpResponseCompletability(prevote, precommit return nil } -// decodeMessage decodes a network-level consensus message into a GRANDPA VoteMessage or FinalizationMessage +// decodeMessage decodes a network-level consensus message into a GRANDPA VoteMessage or CommitMessage func decodeMessage(msg *ConsensusMessage) (m GrandpaMessage, err error) { var ( mi interface{} @@ -274,16 +275,15 @@ func decodeMessage(msg *ConsensusMessage) (m GrandpaMessage, err error) { ) switch msg.Data[0] { - case voteType, precommitType: + case voteType: mi, err = scale.Decode(msg.Data[1:], &VoteMessage{Message: new(SignedMessage)}) if m, ok = mi.(*VoteMessage); !ok { return nil, ErrInvalidMessageType } - case finalizationType: - mi, err = scale.Decode(msg.Data[1:], &FinalizationMessage{Justification: []*SignedPrecommit{}}) - if m, ok = mi.(*FinalizationMessage); !ok { - return nil, ErrInvalidMessageType - } + case commitType: + fmt.Println("decoding commitType") + m = &CommitMessage{} + _, err = scale.Decode(msg.Data[1:], m) case neighbourType: mi, err = scale.Decode(msg.Data[1:], &NeighbourMessage{}) if m, ok = mi.(*NeighbourMessage); !ok { @@ -310,10 +310,19 @@ func decodeMessage(msg *ConsensusMessage) (m GrandpaMessage, err error) { return m, nil } -func (h *MessageHandler) verifyFinalizationMessageJustification(fm *FinalizationMessage) error { - // verify justifications +func (h *MessageHandler) verifyCommitMessageJustification(fm *CommitMessage) error { + if len(fm.Precommits) != len(fm.AuthData) { + return ErrPrecommitSignatureMismatch + } + count := 0 - for _, just := range fm.Justification { + for i, pc := range fm.Precommits { + just := &SignedPrecommit{ + Vote: pc, + Signature: fm.AuthData[i].Signature, + AuthorityID: fm.AuthData[i].AuthorityID, + } + err := h.verifyJustification(just, fm.Round, h.grandpa.state.setID, precommit) if err != nil { continue @@ -327,7 +336,7 @@ func (h *MessageHandler) verifyFinalizationMessageJustification(fm *Finalization // confirm total # signatures >= grandpa threshold if uint64(count) < h.grandpa.state.threshold() { logger.Error("minimum votes not met for finalization message", "votes needed", h.grandpa.state.threshold(), - "votes", fm.Justification) + "votes received", len(fm.Precommits)) return ErrMinVotesNotMet } return nil diff --git a/lib/grandpa/message_handler_test.go b/lib/grandpa/message_handler_test.go index 42271383ec..c00bf08af1 100644 --- a/lib/grandpa/message_handler_test.go +++ b/lib/grandpa/message_handler_test.go @@ -86,8 +86,8 @@ func TestDecodeMessage_VoteMessage(t *testing.T) { expected := &VoteMessage{ Round: 77, SetID: 99, - Stage: precommit, Message: &SignedMessage{ + Stage: precommit, Hash: common.MustHexToHash("0x7db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a"), Number: 0x7777, Signature: sig, @@ -98,29 +98,29 @@ func TestDecodeMessage_VoteMessage(t *testing.T) { require.Equal(t, expected, msg) } -func TestDecodeMessage_FinalizationMessage(t *testing.T) { - cm := &ConsensusMessage{ - Data: common.MustHexToBytes("0x054d000000000000007db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a00000000040a0b0c0d00000000000000000000000000000000000000000000000000000000e70300000102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034602b88f60513f1c805d87ef52896934baf6a662bc37414dbdbf69356b1a691"), - } - - msg, err := decodeMessage(cm) - require.NoError(t, err) - - expected := &FinalizationMessage{ +func TestDecodeMessage_CommitMessage(t *testing.T) { + expected := &CommitMessage{ Round: 77, + SetID: 1, Vote: &Vote{ hash: common.MustHexToHash("0x7db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a"), - number: 0, + number: 99, + }, + Precommits: []*Vote{ + testVote, }, - Justification: []*SignedPrecommit{ + AuthData: []*AuthData{ { - Vote: testVote, Signature: testSignature, AuthorityID: kr.Alice().Public().(*ed25519.PublicKey).AsBytes(), }, }, } + cm, err := expected.ToConsensusMessage() + require.NoError(t, err) + msg, err := decodeMessage(cm) + require.NoError(t, err) require.Equal(t, expected, msg) } @@ -237,14 +237,14 @@ func TestMessageHandler_VerifyJustification_InvalidSig(t *testing.T) { require.Equal(t, err, ErrInvalidSignature) } -func TestMessageHandler_FinalizationMessage_NoCatchUpRequest_ValidSig(t *testing.T) { +func TestMessageHandler_CommitMessage_NoCatchUpRequest_ValidSig(t *testing.T) { gs, st := newTestService(t) round := uint64(77) gs.state.round = round gs.justification[round] = buildTestJustification(t, int(gs.state.threshold()), round, gs.state.setID, kr, precommit) - fm := gs.newFinalizationMessage(gs.head, round) + fm := gs.newCommitMessage(gs.head, round) fm.Vote = NewVote(testHash, uint32(round)) cm, err := fm.ToConsensusMessage() require.NoError(t, err) @@ -263,7 +263,7 @@ func TestMessageHandler_FinalizationMessage_NoCatchUpRequest_ValidSig(t *testing require.Equal(t, fm.Vote.hash, hash) } -func TestMessageHandler_FinalizationMessage_NoCatchUpRequest_MinVoteError(t *testing.T) { +func TestMessageHandler_CommitMessage_NoCatchUpRequest_MinVoteError(t *testing.T) { gs, st := newTestService(t) round := uint64(77) @@ -271,7 +271,7 @@ func TestMessageHandler_FinalizationMessage_NoCatchUpRequest_MinVoteError(t *tes gs.justification[round] = buildTestJustification(t, int(gs.state.threshold()), round, gs.state.setID, kr, precommit) - fm := gs.newFinalizationMessage(gs.head, round) + fm := gs.newCommitMessage(gs.head, round) cm, err := fm.ToConsensusMessage() require.NoError(t, err) @@ -281,7 +281,7 @@ func TestMessageHandler_FinalizationMessage_NoCatchUpRequest_MinVoteError(t *tes require.Nil(t, out) } -func TestMessageHandler_FinalizationMessage_WithCatchUpRequest(t *testing.T) { +func TestMessageHandler_CommitMessage_WithCatchUpRequest(t *testing.T) { gs, st := newTestService(t) gs.justification[77] = []*SignedPrecommit{ @@ -292,7 +292,7 @@ func TestMessageHandler_FinalizationMessage_WithCatchUpRequest(t *testing.T) { }, } - fm := gs.newFinalizationMessage(gs.head, 77) + fm := gs.newCommitMessage(gs.head, 77) cm, err := fm.ToConsensusMessage() require.NoError(t, err) gs.state.voters = gs.state.voters[:1] diff --git a/lib/grandpa/message_test.go b/lib/grandpa/message_test.go index 0b39ad9ce1..f9b61282ec 100644 --- a/lib/grandpa/message_test.go +++ b/lib/grandpa/message_test.go @@ -44,8 +44,8 @@ func TestVoteMessageToConsensusMessage(t *testing.T) { expected := &VoteMessage{ Round: gs.state.round, SetID: gs.state.setID, - Stage: precommit, Message: &SignedMessage{ + Stage: precommit, Hash: v.hash, Number: v.number, AuthorityID: gs.keypair.Public().(*ed25519.PublicKey).AsBytes(), @@ -62,8 +62,8 @@ func TestVoteMessageToConsensusMessage(t *testing.T) { expected = &VoteMessage{ Round: gs.state.round, SetID: gs.state.setID, - Stage: prevote, Message: &SignedMessage{ + Stage: prevote, Hash: v.hash, Number: v.number, AuthorityID: gs.keypair.Public().(*ed25519.PublicKey).AsBytes(), @@ -73,7 +73,7 @@ func TestVoteMessageToConsensusMessage(t *testing.T) { require.Equal(t, expected, vm) } -func TestFinalizationMessageToConsensusMessage(t *testing.T) { +func TestCommitMessageToConsensusMessage(t *testing.T) { gs, _ := newTestService(t) gs.justification[77] = []*SignedPrecommit{ { @@ -83,12 +83,14 @@ func TestFinalizationMessageToConsensusMessage(t *testing.T) { }, } - fm := gs.newFinalizationMessage(gs.head, 77) + fm := gs.newCommitMessage(gs.head, 77) + precommits, authData := justificationToCompact(gs.justification[77]) - expected := &FinalizationMessage{ - Round: 77, - Vote: NewVoteFromHeader(gs.head), - Justification: gs.justification[77], + expected := &CommitMessage{ + Round: 77, + Vote: NewVoteFromHeader(gs.head), + Precommits: precommits, + AuthData: authData, } require.Equal(t, expected, fm) diff --git a/lib/grandpa/network_test.go b/lib/grandpa/network_test.go index 58c9352506..1bcd81ed43 100644 --- a/lib/grandpa/network_test.go +++ b/lib/grandpa/network_test.go @@ -54,7 +54,7 @@ func TestHandleNetworkMessage(t *testing.T) { }, } - fm := gs.newFinalizationMessage(gs.head, 77) + fm := gs.newCommitMessage(gs.head, 77) cm, err := fm.ToConsensusMessage() require.NoError(t, err) gs.state.voters = gs.state.voters[:1] diff --git a/lib/grandpa/round_test.go b/lib/grandpa/round_test.go index 815da4473e..ef5357dd98 100644 --- a/lib/grandpa/round_test.go +++ b/lib/grandpa/round_test.go @@ -58,7 +58,7 @@ func (n *testNetwork) SendMessage(msg NotificationsMessage) { gmsg, err := decodeMessage(cm) require.NoError(n.t, err) - if gmsg.Type() == finalizationType { + if gmsg.Type() == commitType { n.finalized <- gmsg } else { n.out <- gmsg @@ -259,7 +259,7 @@ func TestPlayGrandpaRound_BaseCase(t *testing.T) { wg := sync.WaitGroup{} wg.Add(len(kr.Keys)) - finalized := make([]*FinalizationMessage, len(kr.Keys)) + finalized := make([]*CommitMessage, len(kr.Keys)) for i, fin := range fins { go func(i int, fin <-chan GrandpaMessage) { @@ -267,7 +267,7 @@ func TestPlayGrandpaRound_BaseCase(t *testing.T) { case f := <-fin: // receive first message, which is finalized block from previous round - if f.(*FinalizationMessage).Round == 0 { + if f.(*CommitMessage).Round == 0 { select { case f = <-fin: case <-time.After(testTimeout): @@ -275,7 +275,7 @@ func TestPlayGrandpaRound_BaseCase(t *testing.T) { } } - finalized[i] = f.(*FinalizationMessage) + finalized[i] = f.(*CommitMessage) case <-time.After(testTimeout): t.Errorf("did not receive finalized block from %d", i) @@ -289,9 +289,11 @@ func TestPlayGrandpaRound_BaseCase(t *testing.T) { for _, fb := range finalized { require.NotNil(t, fb) - require.GreaterOrEqual(t, len(fb.Justification), len(kr.Keys)/2) - finalized[0].Justification = []*SignedPrecommit{} - fb.Justification = []*SignedPrecommit{} + require.GreaterOrEqual(t, len(fb.Precommits), len(kr.Keys)/2) + finalized[0].Precommits = []*Vote{} + finalized[0].AuthData = []*AuthData{} + fb.Precommits = []*Vote{} + fb.AuthData = []*AuthData{} require.Equal(t, finalized[0], fb) } } @@ -357,7 +359,7 @@ func TestPlayGrandpaRound_VaryingChain(t *testing.T) { wg := sync.WaitGroup{} wg.Add(len(kr.Keys)) - finalized := make([]*FinalizationMessage, len(kr.Keys)) + finalized := make([]*CommitMessage, len(kr.Keys)) for i, fin := range fins { @@ -366,7 +368,7 @@ func TestPlayGrandpaRound_VaryingChain(t *testing.T) { case f := <-fin: // receive first message, which is finalized block from previous round - if f.(*FinalizationMessage).Round == 0 { + if f.(*CommitMessage).Round == 0 { select { case f = <-fin: case <-time.After(testTimeout): @@ -374,7 +376,7 @@ func TestPlayGrandpaRound_VaryingChain(t *testing.T) { } } - finalized[i] = f.(*FinalizationMessage) + finalized[i] = f.(*CommitMessage) case <-time.After(testTimeout): t.Errorf("did not receive finalized block from %d", i) } @@ -387,9 +389,12 @@ func TestPlayGrandpaRound_VaryingChain(t *testing.T) { for _, fb := range finalized { require.NotNil(t, fb) - require.GreaterOrEqual(t, len(fb.Justification), len(kr.Keys)/2) - finalized[0].Justification = []*SignedPrecommit{} - fb.Justification = []*SignedPrecommit{} + require.GreaterOrEqual(t, len(fb.Precommits), len(kr.Keys)/2) + require.GreaterOrEqual(t, len(fb.AuthData), len(kr.Keys)/2) + finalized[0].Precommits = []*Vote{} + finalized[0].AuthData = []*AuthData{} + fb.Precommits = []*Vote{} + fb.AuthData = []*AuthData{} require.Equal(t, finalized[0], fb) } } @@ -454,7 +459,7 @@ func TestPlayGrandpaRound_OneThirdEquivocating(t *testing.T) { wg := sync.WaitGroup{} wg.Add(len(kr.Keys)) - finalized := make([]*FinalizationMessage, len(kr.Keys)) + finalized := make([]*CommitMessage, len(kr.Keys)) for i, fin := range fins { @@ -463,7 +468,7 @@ func TestPlayGrandpaRound_OneThirdEquivocating(t *testing.T) { case f := <-fin: // receive first message, which is finalized block from previous round - if f.(*FinalizationMessage).Round == 0 { + if f.(*CommitMessage).Round == 0 { select { case f = <-fin: case <-time.After(testTimeout): @@ -471,7 +476,7 @@ func TestPlayGrandpaRound_OneThirdEquivocating(t *testing.T) { } } - finalized[i] = f.(*FinalizationMessage) + finalized[i] = f.(*CommitMessage) case <-time.After(testTimeout): t.Errorf("did not receive finalized block from %d", i) } @@ -484,9 +489,12 @@ func TestPlayGrandpaRound_OneThirdEquivocating(t *testing.T) { for _, fb := range finalized { require.NotNil(t, fb) - require.GreaterOrEqual(t, len(fb.Justification), len(kr.Keys)/2) - finalized[0].Justification = []*SignedPrecommit{} - fb.Justification = []*SignedPrecommit{} + require.GreaterOrEqual(t, len(fb.Precommits), len(kr.Keys)/2) + require.GreaterOrEqual(t, len(fb.AuthData), len(kr.Keys)/2) + finalized[0].Precommits = []*Vote{} + finalized[0].AuthData = []*AuthData{} + fb.Precommits = []*Vote{} + fb.AuthData = []*AuthData{} require.Equal(t, finalized[0], fb) } } @@ -535,7 +543,7 @@ func TestPlayGrandpaRound_MultipleRounds(t *testing.T) { wg := sync.WaitGroup{} wg.Add(len(kr.Keys)) - finalized := make([]*FinalizationMessage, len(kr.Keys)) + finalized := make([]*CommitMessage, len(kr.Keys)) for i, fin := range fins { @@ -544,7 +552,7 @@ func TestPlayGrandpaRound_MultipleRounds(t *testing.T) { case f := <-fin: // receive first message, which is finalized block from previous round - if f.(*FinalizationMessage).Round == uint64(j) { + if f.(*CommitMessage).Round == uint64(j) { select { case f = <-fin: case <-time.After(testTimeout): @@ -552,7 +560,7 @@ func TestPlayGrandpaRound_MultipleRounds(t *testing.T) { } } - finalized[i] = f.(*FinalizationMessage) + finalized[i] = f.(*CommitMessage) case <-time.After(testTimeout): t.Errorf("did not receive finalized block from %d", i) } @@ -567,9 +575,12 @@ func TestPlayGrandpaRound_MultipleRounds(t *testing.T) { for _, fb := range finalized { require.NotNil(t, fb) require.Equal(t, head, fb.Vote.hash) - require.GreaterOrEqual(t, len(fb.Justification), len(kr.Keys)/2) - finalized[0].Justification = []*SignedPrecommit{} - fb.Justification = []*SignedPrecommit{} + require.GreaterOrEqual(t, len(fb.Precommits), len(kr.Keys)/2) + require.GreaterOrEqual(t, len(fb.AuthData), len(kr.Keys)/2) + finalized[0].Precommits = []*Vote{} + finalized[0].AuthData = []*AuthData{} + fb.Precommits = []*Vote{} + fb.AuthData = []*AuthData{} require.Equal(t, finalized[0], fb) } diff --git a/lib/grandpa/types.go b/lib/grandpa/types.go index 45484818b3..8b93c51669 100644 --- a/lib/grandpa/types.go +++ b/lib/grandpa/types.go @@ -30,8 +30,11 @@ import ( type subround byte -var prevote subround = 0 -var precommit subround = 1 +var ( + prevote subround = 0 + precommit subround = 1 + primaryProposal subround = 2 //nolint // TODO: this isn't a subround, maybe rename type, and figure out when it's used +) func (s subround) Encode() ([]byte, error) { return []byte{byte(s)}, nil diff --git a/lib/grandpa/vote_message.go b/lib/grandpa/vote_message.go index 2dbc085026..3b46659204 100644 --- a/lib/grandpa/vote_message.go +++ b/lib/grandpa/vote_message.go @@ -52,7 +52,7 @@ func (s *Service) receiveMessages(cond func() bool) { continue } - logger.Debug("validated vote message", "vote", v, "round", vm.Round, "subround", vm.Stage, "precommits", s.precommits) + logger.Debug("validated vote message", "vote", v, "round", vm.Round, "subround", vm.Message.Stage, "precommits", s.precommits) case <-ctx.Done(): logger.Trace("returning from receiveMessages") return @@ -113,6 +113,7 @@ func (s *Service) createVoteMessage(vote *Vote, stage subround, kp crypto.Keypai } sm := &SignedMessage{ + Stage: stage, Hash: vote.hash, Number: vote.number, Signature: ed25519.NewSignatureBytes(sig), @@ -122,7 +123,6 @@ func (s *Service) createVoteMessage(vote *Vote, stage subround, kp crypto.Keypai return &VoteMessage{ Round: s.state.round, SetID: s.state.setID, - Stage: stage, Message: sm, }, nil } @@ -191,20 +191,20 @@ func (s *Service) validateMessage(m *VoteMessage) (*Vote, error) { } // add justification before checking for equivocation, since equivocatory vote may still be used in justification - if m.Stage == prevote { + if m.Message.Stage == prevote { s.pvJustifications[m.Message.Hash] = append(s.pvJustifications[m.Message.Hash], just) - } else if m.Stage == precommit { + } else if m.Message.Stage == precommit { s.pcJustifications[m.Message.Hash] = append(s.pcJustifications[m.Message.Hash], just) } - equivocated := s.checkForEquivocation(voter, vote, m.Stage) + equivocated := s.checkForEquivocation(voter, vote, m.Message.Stage) if equivocated { return nil, ErrEquivocation } - if m.Stage == prevote { + if m.Message.Stage == prevote { s.prevotes[pk.AsBytes()] = vote - } else if m.Stage == precommit { + } else if m.Message.Stage == precommit { s.precommits[pk.AsBytes()] = vote } @@ -273,7 +273,7 @@ func (s *Service) validateVote(v *Vote) error { func validateMessageSignature(pk *ed25519.PublicKey, m *VoteMessage) error { msg, err := scale.Encode(&FullVote{ - Stage: m.Stage, + Stage: m.Message.Stage, Vote: NewVote(m.Message.Hash, m.Message.Number), Round: m.Round, SetID: m.SetID, From 03651b88d61246a4ba5355f503035a701341f845 Mon Sep 17 00:00:00 2001 From: noot Date: Tue, 20 Apr 2021 15:34:15 -0400 Subject: [PATCH 02/23] add primary proposal sending --- lib/grandpa/grandpa.go | 15 ++++++++++ lib/grandpa/message.go | 50 ++++++++++++++++++++++++++++++++-- lib/grandpa/message_handler.go | 8 ++---- lib/grandpa/types.go | 2 +- 4 files changed, 65 insertions(+), 10 deletions(-) diff --git a/lib/grandpa/grandpa.go b/lib/grandpa/grandpa.go index 75074f69e6..135e8494c2 100644 --- a/lib/grandpa/grandpa.go +++ b/lib/grandpa/grandpa.go @@ -393,6 +393,21 @@ func (s *Service) playGrandpaRound() error { } else { s.network.SendMessage(msg) } + + primProposal, err := s.createVoteMessage(&Vote{ + hash: s.head.Hash(), + number: uint32(s.head.Number.Int64()), + }, primaryProposal, s.keypair) + if err != nil { + logger.Error("failed to create primary proposal message", "error", err) + } else { + msg, err = primProposal.ToConsensusMessage() + if err != nil { + logger.Error("failed to encode finalization message", "error", err) + } else { + s.network.SendMessage(msg) + } + } } logger.Debug("receiving pre-vote messages...") diff --git a/lib/grandpa/message.go b/lib/grandpa/message.go index 112e29e2cf..2a967a94cb 100644 --- a/lib/grandpa/message.go +++ b/lib/grandpa/message.go @@ -67,6 +67,36 @@ func (m *SignedMessage) String() string { return fmt.Sprintf("hash=%s number=%d authorityID=0x%x", m.Hash, m.Number, m.AuthorityID) } +func (m *SignedMessage) Decode(r io.Reader) (err error) { + m.Stage, err = subround(0).Decode(r) + if err != nil { + return err + } + + vote, err := new(Vote).Decode(r) + if err != nil { + return err + } + + m.Hash = vote.hash + m.Number = vote.number + + sig, err := common.Read64Bytes(r) + if err != nil { + return err + } + + copy(m.Signature[:], sig[:]) + + id, err := common.Read32Bytes(r) + if err != nil { + return err + } + + copy(m.AuthorityID[:], id[:]) + return nil +} + // VoteMessage represents a network-level vote message // https://github.com/paritytech/substrate/blob/master/client/finality-grandpa/src/communication/gossip.rs#L336 type VoteMessage struct { @@ -75,6 +105,22 @@ type VoteMessage struct { Message *SignedMessage } +func (v *VoteMessage) Decode(r io.Reader) (err error) { + v.Round, err = common.ReadUint64(r) + if err != nil { + return err + } + + v.SetID, err = common.ReadUint64(r) + if err != nil { + return err + } + + v.Message = new(SignedMessage) + err = v.Message.Decode(r) + return err +} + // Type returns voteType func (v *VoteMessage) Type() byte { return voteType @@ -190,22 +236,20 @@ func (f *CommitMessage) Decode(r io.Reader) (err error) { if err != nil { return err } - fmt.Println(numAuthData) if numAuthData.(*big.Int).Cmp(numPrecommits.(*big.Int)) != 0 { return ErrPrecommitSignatureMismatch } f.AuthData = make([]*AuthData, numAuthData.(*big.Int).Int64()) - fmt.Println(f.AuthData) for i := range f.AuthData { f.AuthData[i] = new(AuthData) err = f.AuthData[i].Decode(r) if err != nil { return err } - fmt.Println(f.AuthData[i]) } + return nil } diff --git a/lib/grandpa/message_handler.go b/lib/grandpa/message_handler.go index d5c4faaa51..b9d0de2ac4 100644 --- a/lib/grandpa/message_handler.go +++ b/lib/grandpa/message_handler.go @@ -18,7 +18,6 @@ package grandpa import ( "bytes" - "fmt" "math/big" "reflect" "sync" @@ -276,12 +275,9 @@ func decodeMessage(msg *ConsensusMessage) (m GrandpaMessage, err error) { switch msg.Data[0] { case voteType: - mi, err = scale.Decode(msg.Data[1:], &VoteMessage{Message: new(SignedMessage)}) - if m, ok = mi.(*VoteMessage); !ok { - return nil, ErrInvalidMessageType - } + m = &VoteMessage{} + _, err = scale.Decode(msg.Data[1:], m) case commitType: - fmt.Println("decoding commitType") m = &CommitMessage{} _, err = scale.Decode(msg.Data[1:], m) case neighbourType: diff --git a/lib/grandpa/types.go b/lib/grandpa/types.go index 8b93c51669..ceb25ea801 100644 --- a/lib/grandpa/types.go +++ b/lib/grandpa/types.go @@ -33,7 +33,7 @@ type subround byte var ( prevote subround = 0 precommit subround = 1 - primaryProposal subround = 2 //nolint // TODO: this isn't a subround, maybe rename type, and figure out when it's used + primaryProposal subround = 2 ) func (s subround) Encode() ([]byte, error) { From 7a0f9b92161e7cf2cc60d78f71b1fa125b7c2fe6 Mon Sep 17 00:00:00 2001 From: noot Date: Tue, 20 Apr 2021 15:45:30 -0400 Subject: [PATCH 03/23] lint --- lib/grandpa/message.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/grandpa/message.go b/lib/grandpa/message.go index 2a967a94cb..f06c705119 100644 --- a/lib/grandpa/message.go +++ b/lib/grandpa/message.go @@ -67,6 +67,7 @@ func (m *SignedMessage) String() string { return fmt.Sprintf("hash=%s number=%d authorityID=0x%x", m.Hash, m.Number, m.AuthorityID) } +// Decode SCALE decodes the data into a SignedMessage func (m *SignedMessage) Decode(r io.Reader) (err error) { m.Stage, err = subround(0).Decode(r) if err != nil { @@ -105,6 +106,7 @@ type VoteMessage struct { Message *SignedMessage } +// Decode SCALE decodes the data into a VoteMessage func (v *VoteMessage) Decode(r io.Reader) (err error) { v.Round, err = common.ReadUint64(r) if err != nil { From 9b3ae8e8e88a4bd3b9fc55f954f6846ed5cf077f Mon Sep 17 00:00:00 2001 From: noot Date: Wed, 21 Apr 2021 15:23:02 -0400 Subject: [PATCH 04/23] fix decoding CommitMessage error --- lib/grandpa/grandpa.go | 28 ++++++++++++++-------------- lib/grandpa/message_handler.go | 7 +++++-- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/lib/grandpa/grandpa.go b/lib/grandpa/grandpa.go index 135e8494c2..ca7b2e31c8 100644 --- a/lib/grandpa/grandpa.go +++ b/lib/grandpa/grandpa.go @@ -394,20 +394,20 @@ func (s *Service) playGrandpaRound() error { s.network.SendMessage(msg) } - primProposal, err := s.createVoteMessage(&Vote{ - hash: s.head.Hash(), - number: uint32(s.head.Number.Int64()), - }, primaryProposal, s.keypair) - if err != nil { - logger.Error("failed to create primary proposal message", "error", err) - } else { - msg, err = primProposal.ToConsensusMessage() - if err != nil { - logger.Error("failed to encode finalization message", "error", err) - } else { - s.network.SendMessage(msg) - } - } + // primProposal, err := s.createVoteMessage(&Vote{ + // hash: s.head.Hash(), + // number: uint32(s.head.Number.Int64()), + // }, primaryProposal, s.keypair) + // if err != nil { + // logger.Error("failed to create primary proposal message", "error", err) + // } else { + // msg, err = primProposal.ToConsensusMessage() + // if err != nil { + // logger.Error("failed to encode finalization message", "error", err) + // } else { + // s.network.SendMessage(msg) + // } + // } } logger.Debug("receiving pre-vote messages...") diff --git a/lib/grandpa/message_handler.go b/lib/grandpa/message_handler.go index b9d0de2ac4..e30f655735 100644 --- a/lib/grandpa/message_handler.go +++ b/lib/grandpa/message_handler.go @@ -278,8 +278,11 @@ func decodeMessage(msg *ConsensusMessage) (m GrandpaMessage, err error) { m = &VoteMessage{} _, err = scale.Decode(msg.Data[1:], m) case commitType: - m = &CommitMessage{} - _, err = scale.Decode(msg.Data[1:], m) + r := &bytes.Buffer{} + _, _ = r.Write(msg.Data[1:]) + cm := &CommitMessage{} + err = cm.Decode(r) + m = cm case neighbourType: mi, err = scale.Decode(msg.Data[1:], &NeighbourMessage{}) if m, ok = mi.(*NeighbourMessage); !ok { From 0cced8779b5d5bdaa312bff2cea13b6280ae9942 Mon Sep 17 00:00:00 2001 From: noot Date: Wed, 21 Apr 2021 15:23:24 -0400 Subject: [PATCH 05/23] readd code --- lib/grandpa/grandpa.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/grandpa/grandpa.go b/lib/grandpa/grandpa.go index ca7b2e31c8..135e8494c2 100644 --- a/lib/grandpa/grandpa.go +++ b/lib/grandpa/grandpa.go @@ -394,20 +394,20 @@ func (s *Service) playGrandpaRound() error { s.network.SendMessage(msg) } - // primProposal, err := s.createVoteMessage(&Vote{ - // hash: s.head.Hash(), - // number: uint32(s.head.Number.Int64()), - // }, primaryProposal, s.keypair) - // if err != nil { - // logger.Error("failed to create primary proposal message", "error", err) - // } else { - // msg, err = primProposal.ToConsensusMessage() - // if err != nil { - // logger.Error("failed to encode finalization message", "error", err) - // } else { - // s.network.SendMessage(msg) - // } - // } + primProposal, err := s.createVoteMessage(&Vote{ + hash: s.head.Hash(), + number: uint32(s.head.Number.Int64()), + }, primaryProposal, s.keypair) + if err != nil { + logger.Error("failed to create primary proposal message", "error", err) + } else { + msg, err = primProposal.ToConsensusMessage() + if err != nil { + logger.Error("failed to encode finalization message", "error", err) + } else { + s.network.SendMessage(msg) + } + } } logger.Debug("receiving pre-vote messages...") From 45e67c6cb32f632aab530134b68b6e9f65a7dcb8 Mon Sep 17 00:00:00 2001 From: noot Date: Wed, 21 Apr 2021 17:36:54 -0400 Subject: [PATCH 06/23] create GrandpaState for managing authorities --- dot/state/grandpa.go | 112 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 dot/state/grandpa.go diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go new file mode 100644 index 0000000000..cf09a37509 --- /dev/null +++ b/dot/state/grandpa.go @@ -0,0 +1,112 @@ +package state + +import ( + "encoding/binary" + "errors" + + "github.com/ChainSafe/chaindb" + "github.com/ChainSafe/gossamer/lib/grandpa" + "github.com/ChainSafe/gossamer/lib/scale" +) + +var ( + grandpaPrefix = "grandpa" + authoritiesPrefix = []byte("auth") + 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 []*grandpa.Voter) (*GrandpaState, error) { + grandpaDB := chaindb.NewTable(db, grandpaPrefix) + s := &GrandpaState{ + baseDB: db, + db: grandpaDB, + } + + err := s.SetAuthorities(1, 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...) +} + +// SetAuthorities sets the authorities for a given setID +func (s *GrandpaState) SetAuthorities(setID uint64, authorities []*grandpa.Voter) 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) ([]*grandpa.Voter, error) { + enc, err := s.db.Get(authoritiesKey(setID)) + if err != nil { + return nil, err + } + + v, err := scale.Decode(enc, []*grandpa.Voter{}) + if err != nil { + return nil, err + } + + return v.([]*grandpa.Voter), nil +} + +func (s *GrandpaState) SetCurrentSetID(setID uint64) error { + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, setID) + return s.db.Put(currentSetIDKey, buf[:]) +} + +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 +} + +// IncrementAndSetAuthorities increments the current set ID and sets the authorities for the new current set ID +func (s *GrandpaState) IncrementAndSetAuthorities(authorities []*grandpa.Voter) error { + currSetID, err := s.GetCurrentSetID() + if err != nil { + return err + } + + newSetID := currSetID + 1 + err = s.SetAuthorities(newSetID, authorities) + if err != nil { + return err + } + + return s.SetCurrentSetID(newSetID) +} From 0687988b321be84e1185aa132102fcc2ce543d00 Mon Sep 17 00:00:00 2001 From: noot Date: Wed, 21 Apr 2021 19:08:11 -0400 Subject: [PATCH 07/23] begin updating DigestHandler to use GrandpaState --- dot/core/digest.go | 98 ++++++++++++++++++++--------------------- dot/core/interface.go | 6 +++ dot/state/grandpa.go | 62 +++++++++++++++++++++----- lib/grandpa/catch_up.go | 1 + 4 files changed, 107 insertions(+), 60 deletions(-) create mode 100644 lib/grandpa/catch_up.go diff --git a/dot/core/digest.go b/dot/core/digest.go index dc00e09b2d..e8625ac313 100644 --- a/dot/core/digest.go +++ b/dot/core/digest.go @@ -22,6 +22,7 @@ import ( "math/big" "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/grandpa" "github.com/ChainSafe/gossamer/lib/scale" ) @@ -33,13 +34,14 @@ 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 + grandpa FinalityGadget + babe BlockProducer + verifier Verifier + // isFinalityAuthority bool + // isBlockProducer bool // block notification channels imported chan *types.Block @@ -82,25 +84,25 @@ func NewDigestHandler(blockState BlockState, epochState EpochState, babe BlockPr return nil, err } - isFinalityAuthority := grandpa != nil - isBlockProducer := babe != nil + // 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, - finalized: finalized, - finalizedID: fid, + ctx: ctx, + cancel: cancel, + blockState: blockState, + epochState: epochState, + grandpa: grandpa, + babe: babe, + verifier: verifier, + // isFinalityAuthority: isFinalityAuthority, + // isBlockProducer: isBlockProducer, + imported: imported, + importedID: iid, + finalized: finalized, + finalizedID: fid, }, nil } @@ -157,7 +159,7 @@ func (h *DigestHandler) HandleConsensusDigest(d *types.ConsensusDigest, header * case types.GrandpaScheduledChangeType: return h.handleScheduledChange(d) case types.GrandpaForcedChangeType: - return h.handleForcedChange(d) + return h.handleForcedChange(d, header) case types.GrandpaOnDisabledType: return h.handleGrandpaOnDisabled(d, header) case types.GrandpaPauseType: @@ -193,9 +195,7 @@ func (h *DigestHandler) handleBlockImport(ctx context.Context) { continue } - if h.isFinalityAuthority { - h.handleGrandpaChangesOnImport(block.Header.Number) - } + h.handleGrandpaChangesOnImport(block.Header.Number) case <-ctx.Done(): return } @@ -210,12 +210,7 @@ func (h *DigestHandler) handleBlockFinalization(ctx context.Context) { continue } - if h.isFinalityAuthority { - h.handleGrandpaChangesOnFinalization(header.Number) - } - - // 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 + h.handleGrandpaChangesOnFinalization(header.Number) case <-ctx.Done(): return } @@ -291,32 +286,35 @@ func (h *DigestHandler) handleScheduledChange(d *types.ConsensusDigest) error { return nil } -func (h *DigestHandler) handleForcedChange(d *types.ConsensusDigest) error { - curr, err := h.blockState.BestBlockHeader() +func (h *DigestHandler) handleForcedChange(d *types.ConsensusDigest, header *types.Header) error { + if d.ConsensusEngineID != types.GrandpaEngineID { + return nil // TODO: maybe error? + } + + 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) - 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) + c, err := newGrandpaChange(fc.Auths, fc.Delay, header.Number) + if err != nil { + return err + } - c, err := newGrandpaChange(fc.Auths, fc.Delay, curr.Number) - if err != nil { - return err - } + h.grandpaForcedChange = c - h.grandpaForcedChange = c + auths, err := types.GrandpaAuthoritiesRawToAuthorities(fc.Auths) + if err != nil { + return err } + h.grandpaState.SetNextChange(grandpa.NewVotersFromAuthorities(auths), big.NewInt(0).Add(header.Number, big.NewInt(int64(fc.Delay)))) return nil } diff --git a/dot/core/interface.go b/dot/core/interface.go index e6c2d43759..458c9e9a72 100644 --- a/dot/core/interface.go +++ b/dot/core/interface.go @@ -22,6 +22,7 @@ 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" @@ -100,3 +101,8 @@ type EpochState interface { SetCurrentEpoch(epoch uint64) error GetCurrentEpoch() (uint64, error) } + +type GrandpaState interface { + SetNextChange(authorities []*grandpa.Voter, number *big.Int) error + //SetSetIDChangeAtBlock(setID uint64, number *big.Int) error +} diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index cf09a37509..b09f3fbb3e 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -3,8 +3,10 @@ package state import ( "encoding/binary" "errors" + "math/big" "github.com/ChainSafe/chaindb" + //"github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/grandpa" "github.com/ChainSafe/gossamer/lib/scale" ) @@ -12,13 +14,15 @@ import ( var ( grandpaPrefix = "grandpa" authoritiesPrefix = []byte("auth") + setIDChangePrefix = []byte("change") currentSetIDKey = []byte("setID") ) // GrandpaState tracks information related to grandpa type GrandpaState struct { - baseDB chaindb.Database - db chaindb.Database + baseDB chaindb.Database + db chaindb.Database + blockState *BlockState } // NewGrandpaStateFromGenesis returns a new GrandpaState given the grandpa genesis authorities @@ -38,10 +42,11 @@ func NewGrandpaStateFromGenesis(db chaindb.Database, genesisAuthorities []*grand } // NewGrandpaState returns a new GrandpaState -func NewGrandpaState(db chaindb.Database) (*GrandpaState, error) { +func NewGrandpaState(db chaindb.Database, blockState *BlockState) (*GrandpaState, error) { return &GrandpaState{ - baseDB: db, - db: chaindb.NewTable(db, grandpaPrefix), + baseDB: db, + db: chaindb.NewTable(db, grandpaPrefix), + blockState: blockState, }, nil } @@ -51,6 +56,12 @@ func authoritiesKey(setID uint64) []byte { 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 []*grandpa.Voter) error { enc, err := scale.Encode(authorities) @@ -95,18 +106,49 @@ func (s *GrandpaState) GetCurrentSetID() (uint64, error) { return binary.LittleEndian.Uint64(id), nil } -// IncrementAndSetAuthorities increments the current set ID and sets the authorities for the new current set ID -func (s *GrandpaState) IncrementAndSetAuthorities(authorities []*grandpa.Voter) error { +// SetNextChange sets the next authority change +func (s *GrandpaState) SetNextChange(authorities []*grandpa.Voter, number *big.Int) error { currSetID, err := s.GetCurrentSetID() if err != nil { return err } - newSetID := currSetID + 1 - err = s.SetAuthorities(newSetID, authorities) + nextSetID := currSetID + 1 + err = s.SetAuthorities(nextSetID, authorities) + if err != nil { + return err + } + + err = s.SetSetIDChangeAtBlock(nextSetID, number) + if err != nil { + return err + } + + //return s.SetCurrentSetID(newSetID) + return nil +} + +func (s *GrandpaState) IncrementSetID() error { + currSetID, err := s.GetCurrentSetID() if err != nil { return err } - return s.SetCurrentSetID(newSetID) + 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 } diff --git a/lib/grandpa/catch_up.go b/lib/grandpa/catch_up.go new file mode 100644 index 0000000000..c99a027682 --- /dev/null +++ b/lib/grandpa/catch_up.go @@ -0,0 +1 @@ +package grandpa From d81b5eb759f6046ea3452beb442def4bb8216ddc Mon Sep 17 00:00:00 2001 From: noot Date: Thu, 22 Apr 2021 11:02:01 -0400 Subject: [PATCH 08/23] fix some tests --- dot/core/digest_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dot/core/digest_test.go b/dot/core/digest_test.go index fd2e1075f8..28c536ab5b 100644 --- a/dot/core/digest_test.go +++ b/dot/core/digest_test.go @@ -64,7 +64,7 @@ func TestDigestHandler_GrandpaScheduledChange(t *testing.T) { handler := newTestDigestHandler(t, false, true) handler.Start() defer handler.Stop() - require.True(t, handler.isFinalityAuthority) + //require.True(t, handler.isFinalityAuthority) kr, err := keystore.NewEd25519Keyring() require.NoError(t, err) From 98cafe9c21d0591fb6e85a029cf3a488dcc491f0 Mon Sep 17 00:00:00 2001 From: noot Date: Thu, 22 Apr 2021 11:49:18 -0400 Subject: [PATCH 09/23] add go.mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 70bb9c6b12..e16612d2e1 100644 --- a/go.mod +++ b/go.mod @@ -58,7 +58,7 @@ require ( golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect - golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd + golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd // indirect google.golang.org/appengine v1.6.5 // indirect google.golang.org/protobuf v1.25.0 ) From 0a1f6a58b553982fe50fdc90ee44e45dcac6e949 Mon Sep 17 00:00:00 2001 From: noot Date: Thu, 22 Apr 2021 14:30:18 -0400 Subject: [PATCH 10/23] move grandpa.Voter to types.GrandpaVoter --- dot/core/digest.go | 7 ++- dot/services.go | 2 +- dot/state/grandpa.go | 14 ++--- dot/types/grandpa.go | 45 +++++++++++++++ lib/grandpa/grandpa.go | 10 ++-- lib/grandpa/grandpa_test.go | 6 +- lib/grandpa/types.go | 97 +++++++++++++++++--------------- lib/grandpa/vote_message.go | 2 +- lib/grandpa/vote_message_test.go | 10 ++-- 9 files changed, 123 insertions(+), 70 deletions(-) diff --git a/dot/core/digest.go b/dot/core/digest.go index e8625ac313..d7d111c497 100644 --- a/dot/core/digest.go +++ b/dot/core/digest.go @@ -22,7 +22,6 @@ import ( "math/big" "github.com/ChainSafe/gossamer/dot/types" - "github.com/ChainSafe/gossamer/lib/grandpa" "github.com/ChainSafe/gossamer/lib/scale" ) @@ -87,6 +86,10 @@ func NewDigestHandler(blockState BlockState, epochState EpochState, babe BlockPr // isFinalityAuthority := grandpa != nil // isBlockProducer := babe != nil + if grandpa == nil { + return nil, errors.New("grandpa is nil") + } + ctx, cancel := context.WithCancel(context.Background()) return &DigestHandler{ @@ -314,7 +317,7 @@ func (h *DigestHandler) handleForcedChange(d *types.ConsensusDigest, header *typ return err } - h.grandpaState.SetNextChange(grandpa.NewVotersFromAuthorities(auths), big.NewInt(0).Add(header.Number, big.NewInt(int64(fc.Delay)))) + h.grandpaState.SetNextChange(types.NewGrandpaVotersFromAuthorities(auths), big.NewInt(0).Add(header.Number, big.NewInt(int64(fc.Delay)))) return nil } diff --git a/dot/services.go b/dot/services.go index e523b88d5d..d3a73a1a2d 100644 --- a/dot/services.go +++ b/dot/services.go @@ -348,7 +348,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 { diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index b09f3fbb3e..1977ae3a0b 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -7,7 +7,7 @@ import ( "github.com/ChainSafe/chaindb" //"github.com/ChainSafe/gossamer/lib/common" - "github.com/ChainSafe/gossamer/lib/grandpa" + "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/scale" ) @@ -26,7 +26,7 @@ type GrandpaState struct { } // NewGrandpaStateFromGenesis returns a new GrandpaState given the grandpa genesis authorities -func NewGrandpaStateFromGenesis(db chaindb.Database, genesisAuthorities []*grandpa.Voter) (*GrandpaState, error) { +func NewGrandpaStateFromGenesis(db chaindb.Database, genesisAuthorities []*types.GrandpaVoter) (*GrandpaState, error) { grandpaDB := chaindb.NewTable(db, grandpaPrefix) s := &GrandpaState{ baseDB: db, @@ -63,7 +63,7 @@ func setIDChangeKey(setID uint64) []byte { } // SetAuthorities sets the authorities for a given setID -func (s *GrandpaState) SetAuthorities(setID uint64, authorities []*grandpa.Voter) error { +func (s *GrandpaState) SetAuthorities(setID uint64, authorities []*types.GrandpaVoter) error { enc, err := scale.Encode(authorities) if err != nil { return err @@ -73,18 +73,18 @@ func (s *GrandpaState) SetAuthorities(setID uint64, authorities []*grandpa.Voter } // GetAuthorities returns the authorities for the given setID -func (s *GrandpaState) GetAuthorities(setID uint64) ([]*grandpa.Voter, error) { +func (s *GrandpaState) GetAuthorities(setID uint64) ([]*types.GrandpaVoter, error) { enc, err := s.db.Get(authoritiesKey(setID)) if err != nil { return nil, err } - v, err := scale.Decode(enc, []*grandpa.Voter{}) + v, err := scale.Decode(enc, []*types.GrandpaVoter{}) if err != nil { return nil, err } - return v.([]*grandpa.Voter), nil + return v.([]*types.GrandpaVoter), nil } func (s *GrandpaState) SetCurrentSetID(setID uint64) error { @@ -107,7 +107,7 @@ func (s *GrandpaState) GetCurrentSetID() (uint64, error) { } // SetNextChange sets the next authority change -func (s *GrandpaState) SetNextChange(authorities []*grandpa.Voter, number *big.Int) error { +func (s *GrandpaState) SetNextChange(authorities []*types.GrandpaVoter, number *big.Int) error { currSetID, err := s.GetCurrentSetID() if err != nil { return err diff --git a/dot/types/grandpa.go b/dot/types/grandpa.go index fa6dd627ac..d194a8966a 100644 --- a/dot/types/grandpa.go +++ b/dot/types/grandpa.go @@ -1,6 +1,7 @@ package types import ( + "fmt" "io" "github.com/ChainSafe/gossamer/lib/common" @@ -58,3 +59,47 @@ func GrandpaAuthoritiesRawToAuthorities(adr []*GrandpaAuthoritiesRaw) ([]*Author return ad, nil } + +// GrandpaVoter represents a GRANDPA voter +type GrandpaVoter struct { + Key *ed25519.PublicKey + ID uint64 //nolint:unused +} + +// 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) +} + +// 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 +} + +// 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 +} diff --git a/lib/grandpa/grandpa.go b/lib/grandpa/grandpa.go index 135e8494c2..5255a8e2c8 100644 --- a/lib/grandpa/grandpa.go +++ b/lib/grandpa/grandpa.go @@ -193,8 +193,8 @@ 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, } } @@ -207,8 +207,8 @@ func (s *Service) UpdateAuthorities(ad []*types.Authority) { for i, a := range ad { if pk, ok := a.Key.(*ed25519.PublicKey); ok { v[i] = &Voter{ - key: pk, - id: a.Weight, + Key: pk, + ID: a.Weight, } } } @@ -386,7 +386,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 finalization message", "error", err) diff --git a/lib/grandpa/grandpa_test.go b/lib/grandpa/grandpa_test.go index 3beab53b47..247fa27224 100644 --- a/lib/grandpa/grandpa_test.go +++ b/lib/grandpa/grandpa_test.go @@ -73,8 +73,8 @@ func newTestVoters() []*Voter { voters := []*Voter{} for i, k := range kr.Keys { voters = append(voters, &Voter{ - key: k.Public().(*ed25519.PublicKey), - id: uint64(i), + Key: k.Public().(*ed25519.PublicKey), + ID: uint64(i), }) } @@ -113,7 +113,7 @@ func TestUpdateAuthorities(t *testing.T) { time.Sleep(time.Second) require.Equal(t, uint64(1), gs.state.setID) require.Equal(t, []*Voter{ - {key: kr.Alice().Public().(*ed25519.PublicKey), id: 0}, + {Key: kr.Alice().Public().(*ed25519.PublicKey), ID: 0}, }, gs.state.voters) gs.UpdateAuthorities([]*types.Authority{ diff --git a/lib/grandpa/types.go b/lib/grandpa/types.go index ceb25ea801..a1d706fa7b 100644 --- a/lib/grandpa/types.go +++ b/lib/grandpa/types.go @@ -28,6 +28,11 @@ import ( "github.com/ChainSafe/gossamer/lib/scale" ) +type ( + Voter = types.GrandpaVoter + Voters = types.GrandpaVoters +) + type subround byte var ( @@ -65,49 +70,49 @@ 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 -} +// // 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 { @@ -131,7 +136,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 +147,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/vote_message.go b/lib/grandpa/vote_message.go index 3b46659204..13f4ef2ed6 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..562cc19dd2 100644 --- a/lib/grandpa/vote_message_test.go +++ b/lib/grandpa/vote_message_test.go @@ -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,7 +99,7 @@ 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) { @@ -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,7 +156,7 @@ 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) { @@ -287,7 +287,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) From 04ebee86d1d9bdf43d5dfe694366577f66b7d6ac Mon Sep 17 00:00:00 2001 From: noot Date: Thu, 22 Apr 2021 15:29:05 -0400 Subject: [PATCH 11/23] update DigestHandler to set grandpaState --- dot/core/digest.go | 36 ++++++++++++++++----------------- dot/core/digest_test.go | 9 +++++++-- dot/core/interface.go | 1 - dot/state/service.go | 15 +++++++++++++- dot/types/grandpa.go | 2 +- lib/grandpa/types.go | 44 ----------------------------------------- 6 files changed, 39 insertions(+), 68 deletions(-) diff --git a/dot/core/digest.go b/dot/core/digest.go index d7d111c497..510f12bace 100644 --- a/dot/core/digest.go +++ b/dot/core/digest.go @@ -39,8 +39,6 @@ type DigestHandler struct { grandpa FinalityGadget babe BlockProducer verifier Verifier - // isFinalityAuthority bool - // isBlockProducer bool // block notification channels imported chan *types.Block @@ -70,7 +68,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, grandpa FinalityGadget, verifier Verifier) (*DigestHandler, error) { imported := make(chan *types.Block, 16) finalized := make(chan *types.Header, 16) iid, err := blockState.RegisterImportedChannel(imported) @@ -83,9 +81,6 @@ func NewDigestHandler(blockState BlockState, epochState EpochState, babe BlockPr return nil, err } - // isFinalityAuthority := grandpa != nil - // isBlockProducer := babe != nil - if grandpa == nil { return nil, errors.New("grandpa is nil") } @@ -93,19 +88,18 @@ func NewDigestHandler(blockState BlockState, epochState EpochState, babe BlockPr 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, - finalized: finalized, - finalizedID: fid, + ctx: ctx, + cancel: cancel, + blockState: blockState, + epochState: epochState, + grandpaState: grandpaState, + grandpa: grandpa, + babe: babe, + verifier: verifier, + imported: imported, + importedID: iid, + finalized: finalized, + finalizedID: fid, }, nil } @@ -294,6 +288,10 @@ func (h *DigestHandler) handleForcedChange(d *types.ConsensusDigest, header *typ return nil // TODO: maybe error? } + if header == nil { + return errors.New("header is nil") + } + if h.grandpaForcedChange != nil { return errors.New("already have forced change scheduled") } diff --git a/dot/core/digest_test.go b/dot/core/digest_test.go index 28c536ab5b..372e1abe49 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" @@ -55,7 +56,7 @@ func newTestDigestHandler(t *testing.T, withBABE, withGrandpa bool) *DigestHandl } time.Sleep(time.Second) - dh, err := NewDigestHandler(stateSrvc.Block, stateSrvc.Epoch, bp, fg, &mockVerifier{}) + dh, err := NewDigestHandler(stateSrvc.Block, stateSrvc.Epoch, stateSrvc.Grandpa, bp, fg, &mockVerifier{}) require.NoError(t, err) return dh } @@ -129,7 +130,11 @@ func TestDigestHandler_GrandpaForcedChange(t *testing.T) { Data: data, } - err = handler.HandleConsensusDigest(d, nil) + header := &types.Header{ + Number: big.NewInt(3), + } + + err = handler.HandleConsensusDigest(d, header) require.NoError(t, err) addTestBlocksToState(t, 2, handler.blockState) diff --git a/dot/core/interface.go b/dot/core/interface.go index 458c9e9a72..02cc6ec975 100644 --- a/dot/core/interface.go +++ b/dot/core/interface.go @@ -104,5 +104,4 @@ type EpochState interface { type GrandpaState interface { SetNextChange(authorities []*grandpa.Voter, number *big.Int) error - //SetSetIDChangeAtBlock(setID uint64, number *big.Int) error } diff --git a/dot/state/service.go b/dot/state/service.go index 67839c740d..95ef1cfc3b 100644 --- a/dot/state/service.go +++ b/dot/state/service.go @@ -46,6 +46,7 @@ type Service struct { Block *BlockState Transaction *TransactionState Epoch *EpochState + Grandpa *GrandpaState closeCh chan interface{} // Below are for testing only. @@ -149,6 +150,12 @@ func (s *Service) Initialize(gen *genesis.Genesis, header *types.Header, t *trie return fmt.Errorf("failed to create epoch state: %s", err) } + // TODO: get authorities from runtime + grandpaState, err := NewGrandpaStateFromGenesis(db, nil) + 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 @@ -158,6 +165,7 @@ func (s *Service) Initialize(gen *genesis.Genesis, header *types.Header, t *trie 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) } @@ -226,7 +234,7 @@ func (s *Service) storeInitialValues(db chaindb.Database, data *genesis.Data, he // Start initializes 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 +320,11 @@ func (s *Service) Start() error { return fmt.Errorf("failed to create epoch state: %w", err) } + s.Grandpa, err = NewGrandpaState(db, s.Block) + 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 d194a8966a..2178e91ec9 100644 --- a/dot/types/grandpa.go +++ b/dot/types/grandpa.go @@ -63,7 +63,7 @@ func GrandpaAuthoritiesRawToAuthorities(adr []*GrandpaAuthoritiesRaw) ([]*Author // GrandpaVoter represents a GRANDPA voter type GrandpaVoter struct { Key *ed25519.PublicKey - ID uint64 //nolint:unused + ID uint64 } // PublicKeyBytes returns the voter key as PublicKeyBytes diff --git a/lib/grandpa/types.go b/lib/grandpa/types.go index a1d706fa7b..b62220f5be 100644 --- a/lib/grandpa/types.go +++ b/lib/grandpa/types.go @@ -70,50 +70,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 From f6ef369459d9589e53ad3568809e4b2ff9c818bd Mon Sep 17 00:00:00 2001 From: noot Date: Fri, 23 Apr 2021 14:16:19 -0400 Subject: [PATCH 12/23] remove grandpa interface from core; update grandpa.Service to get next auths from GrandpaState --- dot/core/digest.go | 17 ++-------- dot/core/digest_test.go | 34 ++++---------------- dot/core/errors.go | 2 +- dot/core/interface.go | 9 ------ dot/core/service.go | 58 +++++++++++++++++----------------- dot/node.go | 4 +-- dot/services.go | 19 +++++------ dot/services_test.go | 26 ++------------- dot/state/grandpa.go | 21 ++++++------ lib/grandpa/catch_up.go | 1 - lib/grandpa/errors.go | 3 ++ lib/grandpa/grandpa.go | 56 ++++++++++++++++++-------------- lib/grandpa/message_handler.go | 2 +- lib/grandpa/state.go | 8 ++++- 14 files changed, 109 insertions(+), 151 deletions(-) delete mode 100644 lib/grandpa/catch_up.go diff --git a/dot/core/digest.go b/dot/core/digest.go index 28fa8e33e6..27a894c41b 100644 --- a/dot/core/digest.go +++ b/dot/core/digest.go @@ -36,7 +36,6 @@ type DigestHandler struct { blockState BlockState epochState EpochState grandpaState GrandpaState - grandpa FinalityGadget babe BlockProducer verifier Verifier @@ -68,7 +67,7 @@ type resume struct { } // NewDigestHandler returns a new DigestHandler -func NewDigestHandler(blockState BlockState, epochState EpochState, grandpaState GrandpaState, 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) finalized := make(chan *types.Header, 16) iid, err := blockState.RegisterImportedChannel(imported) @@ -89,7 +88,6 @@ func NewDigestHandler(blockState BlockState, epochState EpochState, grandpaState blockState: blockState, epochState: epochState, grandpaState: grandpaState, - grandpa: grandpa, babe: babe, verifier: verifier, imported: imported, @@ -114,11 +112,6 @@ func (h *DigestHandler) Stop() { close(h.finalized) } -// 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 { @@ -211,7 +204,7 @@ func (h *DigestHandler) handleBlockFinalization(ctx context.Context) { func (h *DigestHandler) handleGrandpaChangesOnImport(num *big.Int) { resume := h.grandpaResume if resume != nil && num.Cmp(resume.atBlock) == 0 { - h.grandpa.UpdateAuthorities(h.grandpaAuths) + // TODO: update GrandpaState h.grandpaResume = nil } @@ -222,7 +215,6 @@ func (h *DigestHandler) handleGrandpaChangesOnImport(num *big.Int) { logger.Error("failed to increment grandpa set ID", "error", err) } - h.grandpa.UpdateAuthorities(fc.auths) h.grandpaForcedChange = nil } } @@ -230,9 +222,7 @@ func (h *DigestHandler) handleGrandpaChangesOnImport(num *big.Int) { func (h *DigestHandler) handleGrandpaChangesOnFinalization(num *big.Int) { 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{}) + // TODO: update GrandpaState h.grandpaPause = nil } @@ -243,7 +233,6 @@ func (h *DigestHandler) handleGrandpaChangesOnFinalization(num *big.Int) { logger.Error("failed to increment grandpa set ID", "error", err) } - h.grandpa.UpdateAuthorities(sc.auths) h.grandpaScheduledChange = nil } diff --git a/dot/core/digest_test.go b/dot/core/digest_test.go index daf5c1fb08..4839d9305e 100644 --- a/dot/core/digest_test.go +++ b/dot/core/digest_test.go @@ -50,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, stateSrvc.Grandpa, bp, fg, &mockVerifier{}) + dh, err := NewDigestHandler(stateSrvc.Block, stateSrvc.Epoch, stateSrvc.Grandpa, bp, &mockVerifier{}) require.NoError(t, err) return dh } @@ -96,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 { @@ -106,8 +98,10 @@ 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) + // TODO: check authorities were set } func TestDigestHandler_GrandpaForcedChange(t *testing.T) { @@ -141,18 +135,14 @@ func TestDigestHandler_GrandpaForcedChange(t *testing.T) { require.NoError(t, err) addTestBlocksToState(t, 3, handler.blockState) - auths := handler.grandpa.Authorities() - require.Nil(t, auths) // 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)) setID, err := handler.grandpaState.(*state.GrandpaState).GetCurrentSetID() require.NoError(t, err) - require.Equal(t, uint64(2), setID) + require.Equal(t, uint64(1), setID) } func TestDigestHandler_GrandpaPauseAndResume(t *testing.T) { @@ -160,13 +150,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, } @@ -188,8 +171,6 @@ func TestDigestHandler_GrandpaPauseAndResume(t *testing.T) { } time.Sleep(time.Millisecond * 100) - auths := handler.grandpa.Authorities() - require.Equal(t, 0, len(auths)) r := &types.GrandpaResume{ Delay: 3, @@ -208,8 +189,7 @@ 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)) + // TODO: add Pause, Resume to GrandpaState } func TestNextGrandpaAuthorityChange_OneChange(t *testing.T) { diff --git a/dot/core/errors.go b/dot/core/errors.go index fd3b77705e..d9bfcbefc2 100644 --- a/dot/core/errors.go +++ b/dot/core/errors.go @@ -46,7 +46,7 @@ var ErrNilRuntime = errors.New("cannot have nil runtime") var ErrNilBlockProducer = errors.New("cannot have nil BlockProducer") // ErrNilFinalityGadget is returned when trying to instantiate a finalizing Service without a finality gadget -var ErrNilFinalityGadget = errors.New("cannot have nil FinalityGadget") +//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 fd599b3e84..f3a380a822 100644 --- a/dot/core/interface.go +++ b/dot/core/interface.go @@ -24,7 +24,6 @@ import ( "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" ) @@ -69,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 diff --git a/dot/core/service.go b/dot/core/service.go index 93120e87c1..c47242c810 100644 --- a/dot/core/service.go +++ b/dot/core/service.go @@ -59,7 +59,7 @@ type Service struct { isBlockProducer bool // Finality gadget variables - finalityGadget FinalityGadget + //finalityGadget FinalityGadget isFinalityAuthority bool // Block verification @@ -81,17 +81,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 + 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 @@ -121,9 +121,9 @@ func NewService(cfg *Config) (*Service, error) { return nil, ErrNilBlockProducer } - if cfg.IsFinalityAuthority && cfg.FinalityGadget == nil { - return nil, ErrNilFinalityGadget - } + // if cfg.IsFinalityAuthority && cfg.FinalityGadget == nil { + // return nil, ErrNilFinalityGadget + // } h := log.StreamHandler(os.Stdout, log.TerminalFormat()) h = log.CallerFileHandler(h) @@ -148,20 +148,20 @@ 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, + 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{}, diff --git a/dot/node.go b/dot/node.go index 8038119e77..f0eb524850 100644 --- a/dot/node.go +++ b/dot/node.go @@ -248,7 +248,7 @@ 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 + //dh.SetFinalityGadget(fg) // TODO: this should be cleaned up // Syncer syncer, err := createSyncService(cfg, stateSrvc, bp, fg, dh, ver, rt) @@ -259,7 +259,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 e7fba5877c..bac6e3838a 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,13 +222,13 @@ 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, + 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, @@ -358,6 +358,7 @@ 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, @@ -398,5 +399,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, st.Grandpa, 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 index 37b5772aec..c239f26f0a 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -33,7 +33,8 @@ func NewGrandpaStateFromGenesis(db chaindb.Database, genesisAuthorities []*types db: grandpaDB, } - err := s.SetCurrentSetID(1) + // genesis has set ID 0 + err := s.setCurrentSetID(0) if err != nil { return nil, err } @@ -67,8 +68,8 @@ func setIDChangeKey(setID uint64) []byte { return append(setIDChangePrefix, buf...) } -// SetAuthorities sets the authorities for a given setID -func (s *GrandpaState) SetAuthorities(setID uint64, authorities []*types.GrandpaVoter) error { +// 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 @@ -92,8 +93,8 @@ func (s *GrandpaState) GetAuthorities(setID uint64) ([]*types.GrandpaVoter, erro return v.([]*types.GrandpaVoter), nil } -// SetCurrentSetID sets the current set ID -func (s *GrandpaState) SetCurrentSetID(setID uint64) error { +// 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[:]) @@ -121,12 +122,12 @@ func (s *GrandpaState) SetNextChange(authorities []*types.GrandpaVoter, number * } nextSetID := currSetID + 1 - err = s.SetAuthorities(nextSetID, authorities) + err = s.setAuthorities(nextSetID, authorities) if err != nil { return err } - err = s.SetSetIDChangeAtBlock(nextSetID, number) + err = s.setSetIDChangeAtBlock(nextSetID, number) if err != nil { return err } @@ -142,11 +143,11 @@ func (s *GrandpaState) IncrementSetID() error { } nextSetID := currSetID + 1 - return s.SetCurrentSetID(nextSetID) + return s.setCurrentSetID(nextSetID) } -// SetSetIDChangeAtBlock sets a set ID change at a certain block -func (s *GrandpaState) SetSetIDChangeAtBlock(setID uint64, number *big.Int) error { +// 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()) } diff --git a/lib/grandpa/catch_up.go b/lib/grandpa/catch_up.go deleted file mode 100644 index c99a027682..0000000000 --- a/lib/grandpa/catch_up.go +++ /dev/null @@ -1 +0,0 @@ -package grandpa diff --git a/lib/grandpa/errors.go b/lib/grandpa/errors.go index 73042e6b32..ca3485d2f9 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 5255a8e2c8..a4b78512c2 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,7 @@ 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 finalized block - nextAuthorities []*Voter // if not nil, the updated authorities for the next round + //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,6 +82,7 @@ type Service struct { type Config struct { LogLvl log.Lvl BlockState BlockState + GrandpaState GrandpaState DigestHandler DigestHandler Network Network Voters []*Voter @@ -95,6 +97,10 @@ func NewService(cfg *Config) (*Service, error) { return nil, ErrNilBlockState } + if cfg.GrandpaState == nil { + return nil, ErrNilGrandpaState + } + if cfg.DigestHandler == nil { return nil, ErrNilDigestHandler } @@ -131,6 +137,7 @@ func NewService(cfg *Config) (*Service, error) { cancel: cancel, state: NewState(cfg.Voters, cfg.SetID, 0), // TODO: determine current round blockState: cfg.BlockState, + grandpaState: cfg.GrandpaState, digestHandler: cfg.DigestHandler, keypair: cfg.Keypair, authority: cfg.Authority, @@ -188,8 +195,8 @@ 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{ @@ -201,29 +208,27 @@ func (s *Service) Authorities() []*types.Authority { 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++ + s.state.round = 0 + return nil } func (s *Service) publicKeyBytes() ed25519.PublicKeyBytes { @@ -233,7 +238,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() diff --git a/lib/grandpa/message_handler.go b/lib/grandpa/message_handler.go index e30f655735..90830759cb 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/state.go b/lib/grandpa/state.go index 32e8745dca..007077543c 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 { + 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 } From 7a90491db707d08035df89dd5a1c036f68328b61 Mon Sep 17 00:00:00 2001 From: noot Date: Fri, 23 Apr 2021 14:40:31 -0400 Subject: [PATCH 13/23] fix grandpa tests --- dot/core/digest.go | 1 - dot/core/test_helpers.go | 23 ---------------- dot/services.go | 1 - dot/state/grandpa.go | 10 ++++--- dot/types/grandpa.go | 43 +++++++++++++++++++++++++++++ lib/grandpa/grandpa.go | 14 +++++----- lib/grandpa/grandpa_test.go | 46 ++++++++++++++++++-------------- lib/grandpa/round_test.go | 2 +- lib/grandpa/state.go | 2 +- lib/grandpa/types_test.go | 1 - lib/grandpa/vote_message_test.go | 17 ++++++------ 11 files changed, 94 insertions(+), 66 deletions(-) diff --git a/dot/core/digest.go b/dot/core/digest.go index 27a894c41b..3bcf18eccf 100644 --- a/dot/core/digest.go +++ b/dot/core/digest.go @@ -50,7 +50,6 @@ type DigestHandler struct { grandpaForcedChange *grandpaChange grandpaPause *pause grandpaResume *resume - grandpaAuths []*types.Authority // saved in case of pause } type grandpaChange struct { diff --git a/dot/core/test_helpers.go b/dot/core/test_helpers.go index fbe1d6bdd8..e0b70701fb 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/services.go b/dot/services.go index bac6e3838a..4c3fb54ae5 100644 --- a/dot/services.go +++ b/dot/services.go @@ -360,7 +360,6 @@ func createGRANDPAService(cfg *Config, rt runtime.Instance, st *state.Service, d BlockState: st.Block, GrandpaState: st.Grandpa, DigestHandler: dh, - SetID: 1, Voters: voters, Authority: cfg.Core.GrandpaAuthority, Network: net, diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index c239f26f0a..5bcfd612b3 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -1,12 +1,12 @@ package state import ( + "bytes" "encoding/binary" "errors" "math/big" "github.com/ChainSafe/chaindb" - //"github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/scale" ) @@ -39,7 +39,7 @@ func NewGrandpaStateFromGenesis(db chaindb.Database, genesisAuthorities []*types return nil, err } - err = s.SetAuthorities(1, genesisAuthorities) + err = s.setAuthorities(1, genesisAuthorities) if err != nil { return nil, err } @@ -85,12 +85,14 @@ func (s *GrandpaState) GetAuthorities(setID uint64) ([]*types.GrandpaVoter, erro return nil, err } - v, err := scale.Decode(enc, []*types.GrandpaVoter{}) + r := &bytes.Buffer{} + _, _ = r.Write(enc) + v, err := types.DecodeGrandpaVoters(r) if err != nil { return nil, err } - return v.([]*types.GrandpaVoter), nil + return v, nil } // setCurrentSetID sets the current set ID diff --git a/dot/types/grandpa.go b/dot/types/grandpa.go index 2178e91ec9..43f6b77c50 100644 --- a/dot/types/grandpa.go +++ b/dot/types/grandpa.go @@ -6,6 +6,7 @@ import ( "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 @@ -76,6 +77,28 @@ 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)) @@ -103,3 +126,23 @@ func (v GrandpaVoters) String() 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/grandpa.go b/lib/grandpa/grandpa.go index a4b78512c2..4bb3e3a8f8 100644 --- a/lib/grandpa/grandpa.go +++ b/lib/grandpa/grandpa.go @@ -86,7 +86,6 @@ type Config struct { DigestHandler DigestHandler Network Network Voters []*Voter - SetID uint64 Keypair *ed25519.Keypair Authority bool } @@ -130,12 +129,16 @@ 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, @@ -226,8 +229,8 @@ func (s *Service) updateAuthorities() error { } s.state.voters = nextAuthorities - s.state.setID++ - s.state.round = 0 + s.state.setID = currSetID + s.state.round = 1 // round resets to 1 after a set ID change return nil } @@ -261,7 +264,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) diff --git a/lib/grandpa/grandpa_test.go b/lib/grandpa/grandpa_test.go index 247fa27224..076493ff0c 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{ + 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{ + next := []*Voter{ {Key: kr.Alice().Public().(*ed25519.PublicKey), ID: 0}, - }, gs.state.voters) + } - 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/round_test.go b/lib/grandpa/round_test.go index ef5357dd98..d16275ecdb 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 007077543c..f3d88fc18d 100644 --- a/lib/grandpa/state.go +++ b/lib/grandpa/state.go @@ -54,7 +54,7 @@ type BlockState interface { } // GrandpaState is the interface required by grandpa into the grandpa state -type GrandpaState interface { +type GrandpaState interface { //nolint GetCurrentSetID() (uint64, error) GetAuthorities(setID uint64) ([]*types.GrandpaVoter, error) } 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_test.go b/lib/grandpa/vote_message_test.go index 562cc19dd2..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), @@ -104,7 +104,6 @@ func TestCheckForEquivocation_WithEquivocation(t *testing.T) { 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), @@ -161,7 +161,6 @@ func TestCheckForEquivocation_WithExistingEquivocation(t *testing.T) { 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), @@ -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), From 4c96abf0287bb16867f90aa49a692d25bc4fe20a Mon Sep 17 00:00:00 2001 From: noot Date: Fri, 23 Apr 2021 17:26:13 -0400 Subject: [PATCH 14/23] cleanup, add tests --- dot/core/digest.go | 2 + dot/core/digest_test.go | 40 +++++++- dot/core/errors.go | 3 - dot/core/service.go | 23 ++--- dot/node.go | 1 - dot/services.go | 12 +-- dot/state/grandpa.go | 34 +++++-- dot/state/grandpa_test.go | 83 ++++++++++++++++ dot/state/initialize.go | 203 ++++++++++++++++++++++++++++++++++++++ dot/state/service.go | 154 +---------------------------- dot/types/grandpa.go | 19 ++++ 11 files changed, 383 insertions(+), 191 deletions(-) create mode 100644 dot/state/grandpa_test.go create mode 100644 dot/state/initialize.go diff --git a/dot/core/digest.go b/dot/core/digest.go index 3bcf18eccf..db0efe2833 100644 --- a/dot/core/digest.go +++ b/dot/core/digest.go @@ -297,6 +297,8 @@ func (h *DigestHandler) handleForcedChange(d *types.ConsensusDigest, header *typ } fc = dec.(*types.GrandpaForcedChange) + logger.Debug("handling GrandpaForcedChange", "data", fc) + c, err := newGrandpaChange(fc.Auths, fc.Delay, header.Number) if err != nil { return err diff --git a/dot/core/digest_test.go b/dot/core/digest_test.go index 4839d9305e..01d7c7a578 100644 --- a/dot/core/digest_test.go +++ b/dot/core/digest_test.go @@ -101,7 +101,12 @@ func TestDigestHandler_GrandpaScheduledChange(t *testing.T) { setID, err := handler.grandpaState.(*state.GrandpaState).GetCurrentSetID() require.NoError(t, err) require.Equal(t, uint64(1), setID) - // TODO: check authorities were set + + 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) { @@ -143,6 +148,12 @@ func TestDigestHandler_GrandpaForcedChange(t *testing.T) { 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(fc.Auths) + require.NoError(t, err) + require.Equal(t, expected, auths) } func TestDigestHandler_GrandpaPauseAndResume(t *testing.T) { @@ -219,6 +230,13 @@ func TestNextGrandpaAuthorityChange_OneChange(t *testing.T) { 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) { @@ -226,6 +244,9 @@ func TestNextGrandpaAuthorityChange_MultipleChanges(t *testing.T) { handler.Start() defer handler.Stop() + kr, err := keystore.NewEd25519Keyring() + require.NoError(t, err) + later := uint32(6) sc := &types.GrandpaScheduledChange{ Auths: []*types.GrandpaAuthoritiesRaw{}, @@ -247,9 +268,18 @@ func TestNextGrandpaAuthorityChange_MultipleChanges(t *testing.T) { 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(4) fc := &types.GrandpaForcedChange{ - Auths: []*types.GrandpaAuthoritiesRaw{}, + Auths: []*types.GrandpaAuthoritiesRaw{ + {Key: kr.Alice().Public().(*ed25519.PublicKey).AsBytes(), ID: 0}, + }, Delay: earlier, } @@ -266,6 +296,12 @@ func TestNextGrandpaAuthorityChange_MultipleChanges(t *testing.T) { next := handler.NextGrandpaAuthorityChange() 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 d9bfcbefc2..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 finalizing 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/service.go b/dot/core/service.go index c47242c810..b6a4e465df 100644 --- a/dot/core/service.go +++ b/dot/core/service.go @@ -59,8 +59,7 @@ type Service struct { isBlockProducer bool // Finality gadget variables - //finalityGadget FinalityGadget - isFinalityAuthority bool + //isFinalityAuthority bool // Block verification verifier Verifier @@ -91,9 +90,8 @@ type Config struct { Runtime runtime.Instance BlockProducer BlockProducer IsBlockProducer bool - //FinalityGadget FinalityGadget - IsFinalityAuthority bool - Verifier Verifier + //IsFinalityAuthority bool + Verifier Verifier NewBlocks chan types.Block // only used for testing purposes } @@ -121,10 +119,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)) @@ -161,12 +155,11 @@ func NewService(cfg *Config) (*Service, error) { 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, + verifier: cfg.Verifier, + //isFinalityAuthority: cfg.IsFinalityAuthority, + lock: &sync.Mutex{}, + blockAddCh: blockAddCh, + blockAddChID: id, } if cfg.NewBlocks != nil { diff --git a/dot/node.go b/dot/node.go index f0eb524850..d9362bd497 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) diff --git a/dot/services.go b/dot/services.go index 4c3fb54ae5..a89fc11fd1 100644 --- a/dot/services.go +++ b/dot/services.go @@ -228,13 +228,11 @@ func createCoreService(cfg *Config, bp core.BlockProducer, verifier *babe.Verifi 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, + Keystore: ks, + Runtime: rt, + IsBlockProducer: cfg.Core.BabeAuthority, + Verifier: verifier, + Network: net, } // create new core service diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 5bcfd612b3..2f564c73c9 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -1,3 +1,19 @@ +// 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 ( @@ -12,6 +28,7 @@ import ( ) var ( + genesisSetID = uint64(0) grandpaPrefix = "grandpa" authoritiesPrefix = []byte("auth") setIDChangePrefix = []byte("change") @@ -20,9 +37,8 @@ var ( // GrandpaState tracks information related to grandpa type GrandpaState struct { - baseDB chaindb.Database - db chaindb.Database - blockState *BlockState + baseDB chaindb.Database + db chaindb.Database } // NewGrandpaStateFromGenesis returns a new GrandpaState given the grandpa genesis authorities @@ -33,13 +49,12 @@ func NewGrandpaStateFromGenesis(db chaindb.Database, genesisAuthorities []*types db: grandpaDB, } - // genesis has set ID 0 - err := s.setCurrentSetID(0) + err := s.setCurrentSetID(genesisSetID) if err != nil { return nil, err } - err = s.setAuthorities(1, genesisAuthorities) + err = s.setAuthorities(genesisSetID, genesisAuthorities) if err != nil { return nil, err } @@ -48,11 +63,10 @@ func NewGrandpaStateFromGenesis(db chaindb.Database, genesisAuthorities []*types } // NewGrandpaState returns a new GrandpaState -func NewGrandpaState(db chaindb.Database, blockState *BlockState) (*GrandpaState, error) { +func NewGrandpaState(db chaindb.Database) (*GrandpaState, error) { return &GrandpaState{ - baseDB: db, - db: chaindb.NewTable(db, grandpaPrefix), - blockState: blockState, + baseDB: db, + db: chaindb.NewTable(db, grandpaPrefix), }, 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..cef9ab0b77 --- /dev/null +++ b/dot/state/initialize.go @@ -0,0 +1,203 @@ +// 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 ( + "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" +) + +// Initialize initializes 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 initialization of the node; it is not called during normal startup. +func (s *Service) Initialize(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 + + // initialize 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) + } + + granpdaAuths, err := loadGrandpaAuthorities(rt) + if err != nil { + return fmt.Errorf("failed to load grandpa authorities: %w", err) + } + + grandpaState, err := NewGrandpaStateFromGenesis(db, granpdaAuths) + 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(r runtime.Instance) ([]*types.GrandpaVoter, error) { + auths, err := r.GrandpaAuthorities() + if err != nil { + return nil, err + } + + return types.NewGrandpaVotersFromAuthorities(auths), 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 +} + +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 95ef1cfc3b..418ef576ac 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" @@ -83,155 +80,6 @@ func (s *Service) DB() chaindb.Database { return s.db } -// Initialize initializes 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 initialization of the node; it doesn't need to be called during normal startup. -func (s *Service) Initialize(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 - - // initialize 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) - } - - // TODO: get authorities from runtime - grandpaState, err := NewGrandpaStateFromGenesis(db, nil) - 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(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 initializes the Storage database and the Block database. func (s *Service) Start() error { if !s.isMemDB && (s.Storage != nil || s.Block != nil || s.Epoch != nil || s.Grandpa != nil) { @@ -320,7 +168,7 @@ func (s *Service) Start() error { return fmt.Errorf("failed to create epoch state: %w", err) } - s.Grandpa, err = NewGrandpaState(db, s.Block) + s.Grandpa, err = NewGrandpaState(db) if err != nil { return fmt.Errorf("failed to create grandpa state: %w", err) } diff --git a/dot/types/grandpa.go b/dot/types/grandpa.go index 43f6b77c50..65a9af80e2 100644 --- a/dot/types/grandpa.go +++ b/dot/types/grandpa.go @@ -115,6 +115,25 @@ func NewGrandpaVotersFromAuthorities(ad []*Authority) []*GrandpaVoter { 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 From 6ede432e296cf4580583d86e607cf6656228b960 Mon Sep 17 00:00:00 2001 From: noot Date: Fri, 23 Apr 2021 17:27:03 -0400 Subject: [PATCH 15/23] cleanup --- lib/grandpa/grandpa.go | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/grandpa/grandpa.go b/lib/grandpa/grandpa.go index 4bb3e3a8f8..2637d233df 100644 --- a/lib/grandpa/grandpa.go +++ b/lib/grandpa/grandpa.go @@ -67,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 finalized 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 From b96fd85cb7f9b58867d3389701fd94e06addee71 Mon Sep 17 00:00:00 2001 From: noot Date: Fri, 23 Apr 2021 18:11:08 -0400 Subject: [PATCH 16/23] add grandpa pause, resume functionality --- dot/core/digest.go | 8 +++----- dot/core/digest_test.go | 10 +++++++++- dot/core/interface.go | 2 ++ dot/core/service.go | 13 ++++--------- dot/state/grandpa.go | 39 +++++++++++++++++++++++++++++++++++++++ dot/state/initialize.go | 17 ++++++++++------- 6 files changed, 67 insertions(+), 22 deletions(-) diff --git a/dot/core/digest.go b/dot/core/digest.go index db0efe2833..643a6ec6e5 100644 --- a/dot/core/digest.go +++ b/dot/core/digest.go @@ -203,7 +203,6 @@ func (h *DigestHandler) handleBlockFinalization(ctx context.Context) { func (h *DigestHandler) handleGrandpaChangesOnImport(num *big.Int) { resume := h.grandpaResume if resume != nil && num.Cmp(resume.atBlock) == 0 { - // TODO: update GrandpaState h.grandpaResume = nil } @@ -221,7 +220,6 @@ func (h *DigestHandler) handleGrandpaChangesOnImport(num *big.Int) { func (h *DigestHandler) handleGrandpaChangesOnFinalization(num *big.Int) { pause := h.grandpaPause if pause != nil && num.Cmp(pause.atBlock) == 0 { - // TODO: update GrandpaState h.grandpaPause = nil } @@ -279,7 +277,7 @@ func (h *DigestHandler) handleScheduledChange(d *types.ConsensusDigest, header * func (h *DigestHandler) handleForcedChange(d *types.ConsensusDigest, header *types.Header) error { if d.ConsensusEngineID != types.GrandpaEngineID { - return nil // TODO: maybe error? + return nil } if header == nil { @@ -333,7 +331,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 { @@ -355,7 +353,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 01d7c7a578..23a4e7cf9d 100644 --- a/dot/core/digest_test.go +++ b/dot/core/digest_test.go @@ -175,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 { @@ -182,6 +185,7 @@ func TestDigestHandler_GrandpaPauseAndResume(t *testing.T) { } time.Sleep(time.Millisecond * 100) + require.Nil(t, handler.grandpaPause) r := &types.GrandpaResume{ Delay: 3, @@ -200,7 +204,11 @@ func TestDigestHandler_GrandpaPauseAndResume(t *testing.T) { addTestBlocksToState(t, 3, handler.blockState) time.Sleep(time.Millisecond * 110) - // TODO: add Pause, Resume to GrandpaState + 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) { diff --git a/dot/core/interface.go b/dot/core/interface.go index f3a380a822..5c1cd1e52c 100644 --- a/dot/core/interface.go +++ b/dot/core/interface.go @@ -97,4 +97,6 @@ type EpochState interface { 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 b6a4e465df..cf72bdfe65 100644 --- a/dot/core/service.go +++ b/dot/core/service.go @@ -58,9 +58,6 @@ type Service struct { blockProducer BlockProducer isBlockProducer bool - // Finality gadget variables - //isFinalityAuthority bool - // Block verification verifier Verifier @@ -90,8 +87,7 @@ type Config struct { Runtime runtime.Instance BlockProducer BlockProducer IsBlockProducer bool - //IsFinalityAuthority bool - Verifier Verifier + Verifier Verifier NewBlocks chan types.Block // only used for testing purposes } @@ -156,10 +152,9 @@ func NewService(cfg *Config) (*Service, error) { isBlockProducer: cfg.IsBlockProducer, blockProducer: cfg.BlockProducer, verifier: cfg.Verifier, - //isFinalityAuthority: cfg.IsFinalityAuthority, - lock: &sync.Mutex{}, - blockAddCh: blockAddCh, - blockAddChID: id, + lock: &sync.Mutex{}, + blockAddCh: blockAddCh, + blockAddChID: id, } if cfg.NewBlocks != nil { diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 2f564c73c9..a0cebacf44 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -32,6 +32,8 @@ var ( grandpaPrefix = "grandpa" authoritiesPrefix = []byte("auth") setIDChangePrefix = []byte("change") + pauseKey = []byte("pause") + resumeKey = []byte("resume") currentSetIDKey = []byte("setID") ) @@ -176,3 +178,40 @@ func (s *GrandpaState) GetSetIDChange(setID uint64) (*big.Int, error) { 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/initialize.go b/dot/state/initialize.go index cef9ab0b77..43bf2e4f53 100644 --- a/dot/state/initialize.go +++ b/dot/state/initialize.go @@ -17,6 +17,7 @@ package state import ( + "bytes" "fmt" "path/filepath" @@ -103,12 +104,12 @@ func (s *Service) Initialize(gen *genesis.Genesis, header *types.Header, t *trie return fmt.Errorf("failed to create epoch state: %s", err) } - granpdaAuths, err := loadGrandpaAuthorities(rt) + grandpaAuths, err := loadGrandpaAuthorities(t) if err != nil { return fmt.Errorf("failed to load grandpa authorities: %w", err) } - grandpaState, err := NewGrandpaStateFromGenesis(db, granpdaAuths) + grandpaState, err := NewGrandpaStateFromGenesis(db, grandpaAuths) if err != nil { return fmt.Errorf("failed to create grandpa state: %s", err) } @@ -148,13 +149,15 @@ func (s *Service) loadBabeConfigurationFromRuntime(r runtime.Instance) (*types.B return babeCfg, nil } -func loadGrandpaAuthorities(r runtime.Instance) ([]*types.GrandpaVoter, error) { - auths, err := r.GrandpaAuthorities() - if err != nil { - return nil, err +func loadGrandpaAuthorities(t *trie.Trie) ([]*types.GrandpaVoter, error) { + authsRaw := t.Get(runtime.GrandpaAuthoritiesKey) + if authsRaw == nil { + return []*types.GrandpaVoter{}, nil } - return types.NewGrandpaVotersFromAuthorities(auths), nil + r := &bytes.Buffer{} + _, _ = r.Write(authsRaw[1:]) + return types.DecodeGrandpaVoters(r) } // storeInitialValues writes initial genesis values to the state database From 017222d22b889708da709035cb6f675e1544c4d4 Mon Sep 17 00:00:00 2001 From: noot Date: Mon, 26 Apr 2021 12:01:15 -0400 Subject: [PATCH 17/23] fix deepsource issue --- dot/state/grandpa.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index a0cebacf44..24ba51aa62 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -115,7 +115,7 @@ func (s *GrandpaState) GetAuthorities(setID uint64) ([]*types.GrandpaVoter, erro func (s *GrandpaState) setCurrentSetID(setID uint64) error { buf := make([]byte, 8) binary.LittleEndian.PutUint64(buf, setID) - return s.db.Put(currentSetIDKey, buf[:]) + return s.db.Put(currentSetIDKey, buf) } // GetCurrentSetID retrieves the current set ID From 71b41238bf13b249a59e770d250beea431622035 Mon Sep 17 00:00:00 2001 From: noot Date: Mon, 26 Apr 2021 13:33:51 -0400 Subject: [PATCH 18/23] address comments --- dot/core/digest.go | 26 +++++++++++++++++++------- dot/state/grandpa.go | 6 +++++- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/dot/core/digest.go b/dot/core/digest.go index c11e147558..1070c60444 100644 --- a/dot/core/digest.go +++ b/dot/core/digest.go @@ -178,7 +178,10 @@ func (h *DigestHandler) handleBlockImport(ctx context.Context) { continue } - 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 } @@ -193,14 +196,17 @@ func (h *DigestHandler) handleBlockFinalization(ctx context.Context) { continue } - h.handleGrandpaChangesOnFinalization(header.Number) + err := h.handleGrandpaChangesOnFinalization(header.Number) + if err != nil { + logger.Error("failed to handle grandpa changes on block finalization", "error", err) + } 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.grandpaResume = nil @@ -210,14 +216,16 @@ func (h *DigestHandler) handleGrandpaChangesOnImport(num *big.Int) { if fc != nil && num.Cmp(fc.atBlock) == 0 { err := h.grandpaState.IncrementSetID() if err != nil { - logger.Error("failed to increment grandpa set ID", "error", err) + 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 { h.grandpaPause = nil @@ -227,7 +235,7 @@ func (h *DigestHandler) handleGrandpaChangesOnFinalization(num *big.Int) { if sc != nil && num.Cmp(sc.atBlock) == 0 { err := h.grandpaState.IncrementSetID() if err != nil { - logger.Error("failed to increment grandpa set ID", "error", err) + return err } h.grandpaScheduledChange = nil @@ -235,6 +243,7 @@ func (h *DigestHandler) handleGrandpaChangesOnFinalization(num *big.Int) { // if blocks get finalised before forced change takes place, disregard it h.grandpaForcedChange = nil + return nil } func (h *DigestHandler) handleScheduledChange(d *types.ConsensusDigest, header *types.Header) error { @@ -309,7 +318,10 @@ func (h *DigestHandler) handleForcedChange(d *types.ConsensusDigest, header *typ return err } - return h.grandpaState.SetNextChange(types.NewGrandpaVotersFromAuthorities(auths), big.NewInt(0).Add(header.Number, big.NewInt(int64(fc.Delay)))) + 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 { diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 24ba51aa62..9c8a9e8830 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -102,7 +102,11 @@ func (s *GrandpaState) GetAuthorities(setID uint64) ([]*types.GrandpaVoter, erro } r := &bytes.Buffer{} - _, _ = r.Write(enc) + _, err = r.Write(enc) + if err != nil { + return nil, err + } + v, err := types.DecodeGrandpaVoters(r) if err != nil { return nil, err From 3d3f05454e8719f746c206ca8d32665d81f7f32f Mon Sep 17 00:00:00 2001 From: noot Date: Mon, 26 Apr 2021 13:35:15 -0400 Subject: [PATCH 19/23] lint --- dot/core/digest.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dot/core/digest.go b/dot/core/digest.go index 1070c60444..64a61201a7 100644 --- a/dot/core/digest.go +++ b/dot/core/digest.go @@ -99,7 +99,7 @@ func NewDigestHandler(blockState BlockState, epochState EpochState, grandpaState // 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 @@ -188,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: @@ -198,7 +198,7 @@ func (h *DigestHandler) handleBlockFinalization(ctx context.Context) { err := h.handleGrandpaChangesOnFinalization(header.Number) if err != nil { - logger.Error("failed to handle grandpa changes on block finalization", "error", err) + logger.Error("failed to handle grandpa changes on block finalisation", "error", err) } case <-ctx.Done(): return From 567326ad2119a671ee93b085952c56d1e69597bb Mon Sep 17 00:00:00 2001 From: noot Date: Mon, 26 Apr 2021 19:07:52 -0400 Subject: [PATCH 20/23] create BaseState, use in state package --- cmd/gossamer/config.go | 2 +- dot/node.go | 4 +- dot/node_test.go | 2 +- dot/services.go | 4 +- dot/state/{db.go => base.go} | 58 ++++++++++++++--------- dot/state/{db_test.go => base_test.go} | 64 +++++++++----------------- dot/state/block.go | 19 ++++---- dot/state/initialize.go | 15 +++--- dot/state/service.go | 36 ++++++++------- dot/state/storage.go | 8 +--- 10 files changed, 102 insertions(+), 110 deletions(-) rename dot/state/{db.go => base.go} (57%) rename dot/state/{db_test.go => base_test.go} (72%) diff --git a/cmd/gossamer/config.go b/cmd/gossamer/config.go index bc65137174..0131a5a9d9 100644 --- a/cmd/gossamer/config.go +++ b/cmd/gossamer/config.go @@ -747,7 +747,7 @@ func updateDotConfigFromGenesisData(ctx *cli.Context, cfg *dot.Config) error { } // load genesis data from initialised node database - gen, err := state.LoadGenesisData(db) + gen, err := state.NewBaseState(db).LoadGenesisData() if err != nil { return fmt.Errorf("failed to load genesis data: %s", err) } diff --git a/dot/node.go b/dot/node.go index 86cf10da4e..fc6288b6c3 100644 --- a/dot/node.go +++ b/dot/node.go @@ -148,7 +148,7 @@ func NodeInitialized(basepath string, expected bool) bool { } // load genesis data from initialised node database - _, err = state.LoadGenesisData(db) + _, err = state.NewBaseState(db).LoadGenesisData() if err != nil { logger.Warn( "node has not been initialised", @@ -307,7 +307,7 @@ func NewNode(cfg *Config, ks *keystore.GlobalKeystore, stopFunc func()) (*Node, publishMetrics(cfg) } - gd, err := stateSrvc.Storage.GetGenesisData() + gd, err := stateSrvc.Base.LoadGenesisData() if err != nil { return nil, err } diff --git a/dot/node_test.go b/dot/node_test.go index 9afc3175ff..58eb2ea088 100644 --- a/dot/node_test.go +++ b/dot/node_test.go @@ -236,7 +236,7 @@ func TestInitNode_LoadGenesisData(t *testing.T) { require.NoError(t, err) }() - gendata, err := state.LoadGenesisData(stateSrvc.DB()) + gendata, err := stateSrvc.Base.LoadGenesisData() require.NoError(t, err) testGenesis := NewTestGenesis(t) diff --git a/dot/services.go b/dot/services.go index 256f703296..ddfbb28db8 100644 --- a/dot/services.go +++ b/dot/services.go @@ -71,7 +71,7 @@ func createStateService(cfg *Config) (*state.Service, error) { } // load most recent state from database - latestState, err := state.LoadLatestStorageHash(stateSrvc.DB()) + latestState, err := stateSrvc.Base.LoadLatestStorageHash() if err != nil { return nil, fmt.Errorf("failed to load latest state root hash: %s", err) } @@ -327,7 +327,7 @@ func createRPCService(cfg *Config, stateSrvc *state.Service, coreSrvc *core.Serv // System service // creates a service for providing system related information func createSystemService(cfg *types.SystemInfo, stateSrvc *state.Service) (*system.Service, error) { - genesisData, err := stateSrvc.Storage.GetGenesisData() + genesisData, err := stateSrvc.Base.LoadGenesisData() if err != nil { return nil, err } diff --git a/dot/state/db.go b/dot/state/base.go similarity index 57% rename from dot/state/db.go rename to dot/state/base.go index 8b88c64deb..99f74057d6 100644 --- a/dot/state/db.go +++ b/dot/state/base.go @@ -22,19 +22,31 @@ import ( "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/genesis" - "github.com/ChainSafe/gossamer/lib/trie" + //"github.com/ChainSafe/gossamer/lib/trie" - database "github.com/ChainSafe/chaindb" + "github.com/ChainSafe/chaindb" ) +// BaseState is a wrapper for the chaindb.Database, without any prefixes +type BaseState struct { + db chaindb.Database +} + +// NewBaseState returns a new BaseState +func NewBaseState(db chaindb.Database) *BaseState { + return &BaseState{ + db: db, + } +} + // StoreBestBlockHash stores the hash at the BestBlockHashKey -func StoreBestBlockHash(db database.Database, hash common.Hash) error { - return db.Put(common.BestBlockHashKey, hash[:]) +func (s *BaseState) StoreBestBlockHash(hash common.Hash) error { + return s.db.Put(common.BestBlockHashKey, hash[:]) } // LoadBestBlockHash loads the hash stored at BestBlockHashKey -func LoadBestBlockHash(db database.Database) (common.Hash, error) { - hash, err := db.Get(common.BestBlockHashKey) +func (s *BaseState) LoadBestBlockHash() (common.Hash, error) { + hash, err := s.db.Get(common.BestBlockHashKey) if err != nil { return common.Hash{}, err } @@ -43,18 +55,18 @@ func LoadBestBlockHash(db database.Database) (common.Hash, error) { } // StoreGenesisData stores the given genesis data at the known GenesisDataKey. -func StoreGenesisData(db database.Database, gen *genesis.Data) error { +func (s *BaseState) StoreGenesisData(gen *genesis.Data) error { enc, err := json.Marshal(gen) if err != nil { return fmt.Errorf("cannot scale encode genesis data: %s", err) } - return db.Put(common.GenesisDataKey, enc) + return s.db.Put(common.GenesisDataKey, enc) } // LoadGenesisData retrieves the genesis data stored at the known GenesisDataKey. -func LoadGenesisData(db database.Database) (*genesis.Data, error) { - enc, err := db.Get(common.GenesisDataKey) +func (s *BaseState) LoadGenesisData() (*genesis.Data, error) { + enc, err := s.db.Get(common.GenesisDataKey) if err != nil { return nil, err } @@ -69,13 +81,13 @@ func LoadGenesisData(db database.Database) (*genesis.Data, error) { } // StoreLatestStorageHash stores the current root hash in the database at LatestStorageHashKey -func StoreLatestStorageHash(db database.Database, root common.Hash) error { - return db.Put(common.LatestStorageHashKey, root[:]) +func (s *BaseState) StoreLatestStorageHash(root common.Hash) error { + return s.db.Put(common.LatestStorageHashKey, root[:]) } // LoadLatestStorageHash retrieves the hash stored at LatestStorageHashKey from the DB -func LoadLatestStorageHash(db database.Database) (common.Hash, error) { - hashbytes, err := db.Get(common.LatestStorageHashKey) +func (s *BaseState) LoadLatestStorageHash() (common.Hash, error) { + hashbytes, err := s.db.Get(common.LatestStorageHashKey) if err != nil { return common.Hash{}, err } @@ -83,13 +95,13 @@ func LoadLatestStorageHash(db database.Database) (common.Hash, error) { return common.NewHash(hashbytes), nil } -// StoreTrie encodes the entire trie and writes it to the DB -// The key to the DB entry is the root hash of the trie -func StoreTrie(db database.Database, t *trie.Trie) error { - return t.Store(db) -} +// // StoreTrie encodes the entire trie and writes it to the DB +// // The key to the DB entry is the root hash of the trie +// func (s *BaseState) StoreTrie(t *trie.Trie) error { +// return t.Store(s.db) +// } -// LoadTrie loads an encoded trie from the DB where the key is `root` -func LoadTrie(db database.Database, t *trie.Trie, root common.Hash) error { - return t.Load(db, root) -} +// // LoadTrie loads an encoded trie from the DB where the key is `root` +// func (s *BaseState) LoadTrie(t *trie.Trie, root common.Hash) error { +// return t.Load(s.db, root) +// } diff --git a/dot/state/db_test.go b/dot/state/base_test.go similarity index 72% rename from dot/state/db_test.go rename to dot/state/base_test.go index 2a238b8139..a6aee30313 100644 --- a/dot/state/db_test.go +++ b/dot/state/base_test.go @@ -2,7 +2,6 @@ package state import ( "bytes" - "reflect" "testing" "github.com/ChainSafe/gossamer/lib/common" @@ -26,7 +25,7 @@ func TestTrie_StoreAndLoadFromDB(t *testing.T) { } } - err := StoreTrie(db, tt) + err := tt.Store(db) require.NoError(t, err) encroot, err := tt.Hash() @@ -35,7 +34,7 @@ func TestTrie_StoreAndLoadFromDB(t *testing.T) { expected := tt.MustHash() tt = trie.NewEmptyTrie() - err = LoadTrie(db, tt, encroot) + err = tt.Load(db, encroot) require.NoError(t, err) require.Equal(t, expected, tt.MustHash()) } @@ -47,6 +46,7 @@ type test struct { func TestStoreAndLoadLatestStorageHash(t *testing.T) { db := NewInMemoryDB(t) + base := NewBaseState(db) tt := trie.NewEmptyTrie() tests := []test{ @@ -65,27 +65,19 @@ func TestStoreAndLoadLatestStorageHash(t *testing.T) { } expected, err := tt.Hash() - if err != nil { - t.Fatal(err) - } - - err = StoreLatestStorageHash(db, expected) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - hash, err := LoadLatestStorageHash(db) - if err != nil { - t.Fatal(err) - } + err = base.StoreLatestStorageHash(expected) + require.NoError(t, err) - if hash != expected { - t.Fatalf("Fail: got %x expected %x", hash, expected) - } + hash, err := base.LoadLatestStorageHash() + require.NoError(t, err) + require.Equal(t, expected, hash) } func TestStoreAndLoadGenesisData(t *testing.T) { db := NewInMemoryDB(t) + base := NewBaseState(db) bootnodes := common.StringArrayToBytes([]string{ "/ip4/127.0.0.1/tcp/7001/p2p/12D3KooWHHzSeKaY8xuZVzkLbKFfvNgPPeKhFBGrMbNzbm5akpqu", @@ -99,36 +91,24 @@ func TestStoreAndLoadGenesisData(t *testing.T) { ProtocolID: "/gossamer/test/0", } - err := StoreGenesisData(db, expected) - if err != nil { - t.Fatal(err) - } - - gen, err := LoadGenesisData(db) - if err != nil { - t.Fatal(err) - } + err := base.StoreGenesisData(expected) + require.NoError(t, err) - if !reflect.DeepEqual(gen, expected) { - t.Fatalf("Fail: got %v expected %v", gen, expected) - } + gen, err := base.LoadGenesisData() + require.NoError(t, err) + require.Equal(t, expected, gen) } func TestStoreAndLoadBestBlockHash(t *testing.T) { db := NewInMemoryDB(t) - hash, _ := common.HexToHash("0x3f5a19b9e9507e05276216f3877bb289e47885f8184010c65d0e41580d3663cc") + base := NewBaseState(db) - err := StoreBestBlockHash(db, hash) - if err != nil { - t.Fatal(err) - } + hash, _ := common.HexToHash("0x3f5a19b9e9507e05276216f3877bb289e47885f8184010c65d0e41580d3663cc") - res, err := LoadBestBlockHash(db) - if err != nil { - t.Fatal(err) - } + err := base.StoreBestBlockHash(hash) + require.NoError(t, err) - if !reflect.DeepEqual(res, hash) { - t.Fatalf("Fail: got %x expected %x", res, hash) - } + res, err := base.LoadBestBlockHash() + require.NoError(t, err) + require.Equal(t, hash, res) } diff --git a/dot/state/block.go b/dot/state/block.go index 2ed76e7a87..16406d066e 100644 --- a/dot/state/block.go +++ b/dot/state/block.go @@ -38,9 +38,10 @@ const pruneKeyBufferSize = 1000 // BlockState defines fields for manipulating the state of blocks, such as BlockTree, BlockDB and Header type BlockState struct { - bt *blocktree.BlockTree - baseDB chaindb.Database - db chaindb.Database + bt *blocktree.BlockTree + //baseDB chaindb.Database + baseState *BaseState + db chaindb.Database sync.RWMutex genesisHash common.Hash @@ -61,7 +62,7 @@ func NewBlockState(db chaindb.Database, bt *blocktree.BlockTree) (*BlockState, e bs := &BlockState{ bt: bt, - baseDB: db, + baseState: NewBaseState(db), db: chaindb.NewTable(db, blockPrefix), imported: make(map[byte]chan<- *types.Block), finalised: make(map[byte]chan<- *types.Header), @@ -81,7 +82,7 @@ func NewBlockState(db chaindb.Database, bt *blocktree.BlockTree) (*BlockState, e func NewBlockStateFromGenesis(db chaindb.Database, header *types.Header) (*BlockState, error) { bs := &BlockState{ bt: blocktree.NewBlockTreeFromRoot(header, db), - baseDB: db, + baseState: NewBaseState(db), db: chaindb.NewTable(db, blockPrefix), imported: make(map[byte]chan<- *types.Block), finalised: make(map[byte]chan<- *types.Header), @@ -548,7 +549,7 @@ func (bs *BlockState) AddBlockWithArrivalTime(block *types.Block, arrivalTime ti } go bs.notifyImported(block) - return bs.baseDB.Flush() + return bs.db.Flush() } // handleAddedBlock re-sets the canonical number->hash mapping if there was a chain re-org. @@ -717,7 +718,7 @@ func (bs *BlockState) BlocktreeAsString() string { } func (bs *BlockState) setBestBlockHashKey(hash common.Hash) error { - return StoreBestBlockHash(bs.baseDB, hash) + return bs.baseState.StoreBestBlockHash(hash) } // HasArrivalTime returns true if the db contains the block's arrival time @@ -727,7 +728,7 @@ func (bs *BlockState) HasArrivalTime(hash common.Hash) (bool, error) { // GetArrivalTime returns the arrival time in nanoseconds since the Unix epoch of a block given its hash func (bs *BlockState) GetArrivalTime(hash common.Hash) (time.Time, error) { - arrivalTime, err := bs.baseDB.Get(arrivalTimeKey(hash)) + arrivalTime, err := bs.db.Get(arrivalTimeKey(hash)) if err != nil { return time.Time{}, err } @@ -739,5 +740,5 @@ func (bs *BlockState) GetArrivalTime(hash common.Hash) (time.Time, error) { func (bs *BlockState) setArrivalTime(hash common.Hash, arrivalTime time.Time) error { buf := make([]byte, 8) binary.LittleEndian.PutUint64(buf, uint64(arrivalTime.UnixNano())) - return bs.baseDB.Put(arrivalTimeKey(hash), buf) + return bs.db.Put(arrivalTimeKey(hash), buf) } diff --git a/dot/state/initialize.go b/dot/state/initialize.go index 8ed36fe2c5..c1e73cedc1 100644 --- a/dot/state/initialize.go +++ b/dot/state/initialize.go @@ -56,6 +56,7 @@ func (s *Service) Initialise(gen *genesis.Genesis, header *types.Header, t *trie if err != nil { return fmt.Errorf("failed to create database: %s", err) } + s.db = db if err = db.ClearAll(); err != nil { return fmt.Errorf("failed to clear database: %s", err) @@ -65,6 +66,8 @@ func (s *Service) Initialise(gen *genesis.Genesis, header *types.Header, t *trie return fmt.Errorf("failed to write genesis trie to database: %w", err) } + s.Base = NewBaseState(db) + rt, err := s.createGenesisRuntime(t, gen) if err != nil { return err @@ -76,7 +79,7 @@ func (s *Service) Initialise(gen *genesis.Genesis, header *types.Header, t *trie } // write initial genesis values to database - if err = s.storeInitialValues(db, gen.GenesisData(), header, t); err != nil { + if err = s.storeInitialValues(gen.GenesisData(), header, t); err != nil { return fmt.Errorf("failed to write genesis values to database: %s", err) } @@ -161,24 +164,24 @@ func loadGrandpaAuthorities(t *trie.Trie) ([]*types.GrandpaVoter, error) { } // 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 { +func (s *Service) storeInitialValues(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 { + if err := t.Store(chaindb.NewTable(s.db, storagePrefix)); 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 { + if err := s.Base.StoreLatestStorageHash(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 { + if err := s.Base.StoreBestBlockHash(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 { + if err := s.Base.StoreGenesisData(data); err != nil { return fmt.Errorf("failed to write genesis data to database: %s", err) } diff --git a/dot/state/service.go b/dot/state/service.go index 39be20ce74..4bfb44189e 100644 --- a/dot/state/service.go +++ b/dot/state/service.go @@ -39,6 +39,7 @@ type Service struct { logLvl log.Lvl db chaindb.Database isMemDB bool // set to true if using an in-memory database; only used for testing. + Base *BaseState Storage *StorageState Block *BlockState Transaction *TransactionState @@ -104,10 +105,11 @@ func (s *Service) Start() error { } s.db = db + s.Base = NewBaseState(db) } // retrieve latest header - bestHash, err := LoadBestBlockHash(db) + bestHash, err := s.Base.LoadBestBlockHash() if err != nil { return fmt.Errorf("failed to get best block hash: %w", err) } @@ -146,7 +148,7 @@ func (s *Service) Start() error { return fmt.Errorf("failed to create storage state: %w", err) } - stateRoot, err := LoadLatestStorageHash(s.db) + stateRoot, err := s.Base.LoadLatestStorageHash() if err != nil { return fmt.Errorf("cannot load latest storage root: %w", err) } @@ -216,7 +218,7 @@ func (s *Service) Rewind(toBlock int64) error { return err } - return StoreBestBlockHash(s.db, newHead) + return s.Base.StoreBestBlockHash(newHead) } // Stop closes each state database @@ -234,13 +236,13 @@ func (s *Service) Stop() error { return errTrieDoesNotExist(head) } - if err = StoreLatestStorageHash(s.db, head); err != nil { + if err = s.Base.StoreLatestStorageHash(head); err != nil { return err } logger.Debug("storing latest storage trie", "root", head) - if err = StoreTrie(s.Storage.db, t); err != nil { + if err = t.Store(s.Storage.db); err != nil { return err } @@ -249,7 +251,7 @@ func (s *Service) Stop() error { } hash := s.Block.BestBlockHash() - if err = StoreBestBlockHash(s.db, hash); err != nil { + if err = s.Base.StoreBestBlockHash(hash); err != nil { return err } @@ -277,14 +279,13 @@ func (s *Service) Import(header *types.Header, t *trie.Trie, firstSlot uint64) e if s.isMemDB { cfg.InMemory = true - } else { - var err error + } - // initialise database using data directory - s.db, err = chaindb.NewBadgerDB(cfg) - if err != nil { - return fmt.Errorf("failed to create database: %s", err) - } + var err error + // initialise database using data directory + s.db, err = chaindb.NewBadgerDB(cfg) + if err != nil { + return fmt.Errorf("failed to create database: %s", err) } block := &BlockState{ @@ -300,10 +301,11 @@ func (s *Service) Import(header *types.Header, t *trie.Trie, firstSlot uint64) e return err } - logger.Info("storing first slot...", "slot", firstSlot) + logger.Info("storing first slot...", "slot", firstSlot, "db", s.db) if err = storeFirstSlot(s.db, firstSlot); err != nil { return err } + logger.Info("stored first slot...", "slot", firstSlot, "db", s.db) epoch.firstSlot = firstSlot blockEpoch, err := epoch.GetEpochForBlock(header) @@ -327,13 +329,13 @@ func (s *Service) Import(header *types.Header, t *trie.Trie, firstSlot uint64) e return fmt.Errorf("trie state root does not equal header state root") } - if err := StoreLatestStorageHash(s.db, root); err != nil { + if err := s.Base.StoreLatestStorageHash(root); err != nil { return err } logger.Info("importing storage trie...", "basepath", s.dbPath, "root", root) - if err := StoreTrie(storage.db, t); err != nil { + if err := t.Store(storage.db); err != nil { return err } @@ -342,7 +344,7 @@ func (s *Service) Import(header *types.Header, t *trie.Trie, firstSlot uint64) e return err } - if err := StoreBestBlockHash(s.db, header.Hash()); err != nil { + if err := s.Base.StoreBestBlockHash(header.Hash()); err != nil { return err } diff --git a/dot/state/storage.go b/dot/state/storage.go index e00a2468b0..38567f9b54 100644 --- a/dot/state/storage.go +++ b/dot/state/storage.go @@ -25,7 +25,6 @@ import ( "github.com/ChainSafe/chaindb" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" - "github.com/ChainSafe/gossamer/lib/genesis" rtstorage "github.com/ChainSafe/gossamer/lib/runtime/storage" "github.com/ChainSafe/gossamer/lib/trie" ) @@ -162,7 +161,7 @@ func (s *StorageState) TrieState(root *common.Hash) (*rtstorage.TrieState, error // LoadFromDB loads an encoded trie from the DB where the key is `root` func (s *StorageState) LoadFromDB(root common.Hash) (*trie.Trie, error) { t := trie.NewEmptyTrie() - err := LoadTrie(s.db, t, root) + err := t.Load(s.db, root) if err != nil { return nil, err } @@ -385,8 +384,3 @@ func (s *StorageState) pruneStorage(closeCh chan interface{}) { } } } - -// GetGenesisData retrieves current genesis data from database -func (s *StorageState) GetGenesisData() (*genesis.Data, error) { - return LoadGenesisData(s.baseDB) -} From ba37ecfe9b7a11f8efac4d5c072b2acbb08218b8 Mon Sep 17 00:00:00 2001 From: noot Date: Tue, 27 Apr 2021 14:51:00 -0400 Subject: [PATCH 21/23] fix state.Service.Import --- dot/state/service.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dot/state/service.go b/dot/state/service.go index 4bfb44189e..fecd187dc7 100644 --- a/dot/state/service.go +++ b/dot/state/service.go @@ -301,11 +301,11 @@ func (s *Service) Import(header *types.Header, t *trie.Trie, firstSlot uint64) e return err } - logger.Info("storing first slot...", "slot", firstSlot, "db", s.db) + s.Base = NewBaseState(s.db) + if err = storeFirstSlot(s.db, firstSlot); err != nil { return err } - logger.Info("stored first slot...", "slot", firstSlot, "db", s.db) epoch.firstSlot = firstSlot blockEpoch, err := epoch.GetEpochForBlock(header) From d768ffe883d8f37ef7cdc5a74e0c921dec8ce094 Mon Sep 17 00:00:00 2001 From: noot Date: Tue, 27 Apr 2021 16:49:07 -0400 Subject: [PATCH 22/23] update remaining states and tests --- cmd/gossamer/config_test.go | 2 +- dot/state/base.go | 38 +++++++++++++++++++++------- dot/state/epoch.go | 50 +++++++++---------------------------- dot/state/grandpa.go | 9 +++---- dot/state/service.go | 4 +-- dot/state/service_test.go | 2 ++ dot/state/storage.go | 6 ++--- 7 files changed, 51 insertions(+), 60 deletions(-) diff --git a/cmd/gossamer/config_test.go b/cmd/gossamer/config_test.go index c0485bc8a5..6c87cb8d06 100644 --- a/cmd/gossamer/config_test.go +++ b/cmd/gossamer/config_test.go @@ -822,7 +822,7 @@ func TestUpdateConfigFromGenesisData(t *testing.T) { gen, err := genesis.NewGenesisFromJSONRaw(genFile.Name()) require.Nil(t, err) - err = state.StoreGenesisData(db, gen.GenesisData()) + err = state.NewBaseState(db).StoreGenesisData(gen.GenesisData()) require.Nil(t, err) err = db.Close() diff --git a/dot/state/base.go b/dot/state/base.go index 99f74057d6..8839092078 100644 --- a/dot/state/base.go +++ b/dot/state/base.go @@ -17,6 +17,7 @@ package state import ( + "encoding/binary" "encoding/json" "fmt" @@ -95,13 +96,32 @@ func (s *BaseState) LoadLatestStorageHash() (common.Hash, error) { return common.NewHash(hashbytes), nil } -// // StoreTrie encodes the entire trie and writes it to the DB -// // The key to the DB entry is the root hash of the trie -// func (s *BaseState) StoreTrie(t *trie.Trie) error { -// return t.Store(s.db) -// } +func (s *BaseState) storeSkipToEpoch(epoch uint64) error { + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, epoch) + return s.db.Put(skipToKey, buf) +} + +func (s *BaseState) loadSkipToEpoch() (uint64, error) { + data, err := s.db.Get(skipToKey) + if err != nil { + return 0, err + } + + return binary.LittleEndian.Uint64(data), nil +} -// // LoadTrie loads an encoded trie from the DB where the key is `root` -// func (s *BaseState) LoadTrie(t *trie.Trie, root common.Hash) error { -// return t.Load(s.db, root) -// } +func (s *BaseState) storeFirstSlot(slot uint64) error { + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, slot) + return s.db.Put(firstSlotKey, buf) +} + +func (s *BaseState) loadFirstSlot() (uint64, error) { + data, err := s.db.Get(firstSlotKey) + if err != nil { + return 0, err + } + + return binary.LittleEndian.Uint64(data), nil +} diff --git a/dot/state/epoch.go b/dot/state/epoch.go index 9afe2cb996..fc45af6d9a 100644 --- a/dot/state/epoch.go +++ b/dot/state/epoch.go @@ -51,8 +51,8 @@ func configDataKey(epoch uint64) []byte { // EpochState tracks information related to each epoch type EpochState struct { - baseDB chaindb.Database db chaindb.Database + baseState *BaseState epochLength uint64 // measured in slots firstSlot uint64 skipToEpoch uint64 @@ -60,7 +60,9 @@ type EpochState struct { // NewEpochStateFromGenesis returns a new EpochState given information for the first epoch, fetched from the runtime func NewEpochStateFromGenesis(db chaindb.Database, genesisConfig *types.BabeConfiguration) (*EpochState, error) { - err := storeFirstSlot(db, 1) // this may change once the first block is imported + baseState := NewBaseState(db) + + err := baseState.storeFirstSlot(1) // this may change once the first block is imported if err != nil { return nil, err } @@ -76,7 +78,7 @@ func NewEpochStateFromGenesis(db chaindb.Database, genesisConfig *types.BabeConf } s := &EpochState{ - baseDB: db, + baseState: NewBaseState(db), db: epochDB, epochLength: genesisConfig.EpochLength, firstSlot: 1, @@ -109,7 +111,7 @@ func NewEpochStateFromGenesis(db chaindb.Database, genesisConfig *types.BabeConf return nil, err } - if err := storeSkipToEpoch(db, 0); err != nil { + if err := s.baseState.storeSkipToEpoch(0); err != nil { return nil, err } @@ -118,23 +120,25 @@ func NewEpochStateFromGenesis(db chaindb.Database, genesisConfig *types.BabeConf // NewEpochState returns a new EpochState func NewEpochState(db chaindb.Database) (*EpochState, error) { + baseState := NewBaseState(db) + epochLength, err := loadEpochLength(db) if err != nil { return nil, err } - firstSlot, err := loadFirstSlot(db) + firstSlot, err := baseState.loadFirstSlot() if err != nil { return nil, err } - skipToEpoch, err := loadSkipToEpoch(db) + skipToEpoch, err := baseState.loadSkipToEpoch() if err != nil { return nil, err } return &EpochState{ - baseDB: db, + baseState: baseState, db: chaindb.NewTable(db, epochPrefix), epochLength: epochLength, firstSlot: firstSlot, @@ -157,21 +161,6 @@ func loadEpochLength(db chaindb.Database) (uint64, error) { return binary.LittleEndian.Uint64(data), nil } -func storeFirstSlot(db chaindb.Database, slot uint64) error { - buf := make([]byte, 8) - binary.LittleEndian.PutUint64(buf, slot) - return db.Put(firstSlotKey, buf) -} - -func loadFirstSlot(db chaindb.Database) (uint64, error) { - data, err := db.Get(firstSlotKey) - if err != nil { - return 0, err - } - - return binary.LittleEndian.Uint64(data), nil -} - // SetCurrentEpoch sets the current epoch func (s *EpochState) SetCurrentEpoch(epoch uint64) error { buf := make([]byte, 8) @@ -301,22 +290,7 @@ func (s *EpochState) GetStartSlotForEpoch(epoch uint64) (uint64, error) { // SetFirstSlot sets the first slot number of the network func (s *EpochState) SetFirstSlot(slot uint64) error { s.firstSlot = slot - return storeFirstSlot(s.baseDB, slot) -} - -func storeSkipToEpoch(db chaindb.Database, epoch uint64) error { - buf := make([]byte, 8) - binary.LittleEndian.PutUint64(buf, epoch) - return db.Put(skipToKey, buf) -} - -func loadSkipToEpoch(db chaindb.Database) (uint64, error) { - data, err := db.Get(skipToKey) - if err != nil { - return 0, err - } - - return binary.LittleEndian.Uint64(data), nil + return s.baseState.storeFirstSlot(slot) } // SkipVerify returns whether verification for the given header should be skipped or not. diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 9c8a9e8830..cc159bdf2c 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -39,16 +39,14 @@ var ( // GrandpaState tracks information related to grandpa type GrandpaState struct { - baseDB chaindb.Database - db 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, + db: grandpaDB, } err := s.setCurrentSetID(genesisSetID) @@ -67,8 +65,7 @@ func NewGrandpaStateFromGenesis(db chaindb.Database, genesisAuthorities []*types // NewGrandpaState returns a new GrandpaState func NewGrandpaState(db chaindb.Database) (*GrandpaState, error) { return &GrandpaState{ - baseDB: db, - db: chaindb.NewTable(db, grandpaPrefix), + db: chaindb.NewTable(db, grandpaPrefix), }, nil } diff --git a/dot/state/service.go b/dot/state/service.go index fecd187dc7..7f69cc3d7f 100644 --- a/dot/state/service.go +++ b/dot/state/service.go @@ -303,7 +303,7 @@ func (s *Service) Import(header *types.Header, t *trie.Trie, firstSlot uint64) e s.Base = NewBaseState(s.db) - if err = storeFirstSlot(s.db, firstSlot); err != nil { + if err = s.Base.storeFirstSlot(firstSlot); err != nil { return err } @@ -315,7 +315,7 @@ func (s *Service) Import(header *types.Header, t *trie.Trie, firstSlot uint64) e skipTo := blockEpoch + 1 - if err := storeSkipToEpoch(s.db, skipTo); err != nil { + if err := s.Base.storeSkipToEpoch(skipTo); err != nil { return err } logger.Debug("skip BABE verification up to epoch", "epoch", skipTo) diff --git a/dot/state/service_test.go b/dot/state/service_test.go index fff41739f6..72b4c94f7f 100644 --- a/dot/state/service_test.go +++ b/dot/state/service_test.go @@ -248,6 +248,8 @@ func TestService_Import(t *testing.T) { genData, genTrie, genesisHeader := newTestGenesisWithTrieAndHeader(t) err := serv.Initialise(genData, genesisHeader, genTrie) require.NoError(t, err) + err = serv.db.Close() + require.NoError(t, err) tr := trie.NewEmptyTrie() var testCases = []string{ diff --git a/dot/state/storage.go b/dot/state/storage.go index 38567f9b54..1aefb4f8ac 100644 --- a/dot/state/storage.go +++ b/dot/state/storage.go @@ -44,9 +44,8 @@ type StorageState struct { blockState *BlockState tries map[common.Hash]*trie.Trie // map of root -> trie - baseDB chaindb.Database - db chaindb.Database - lock sync.RWMutex + db chaindb.Database + lock sync.RWMutex // change notifiers changedLock sync.RWMutex @@ -71,7 +70,6 @@ func NewStorageState(db chaindb.Database, blockState *BlockState, t *trie.Trie) return &StorageState{ blockState: blockState, tries: tries, - baseDB: db, db: chaindb.NewTable(db, storagePrefix), observerList: []Observer{}, }, nil From 360bc2747755acf4a720c56f5fabd9afa76ea302 Mon Sep 17 00:00:00 2001 From: noot Date: Tue, 27 Apr 2021 16:49:39 -0400 Subject: [PATCH 23/23] lint --- dot/state/base.go | 1 - 1 file changed, 1 deletion(-) diff --git a/dot/state/base.go b/dot/state/base.go index 8839092078..32f338d5d3 100644 --- a/dot/state/base.go +++ b/dot/state/base.go @@ -23,7 +23,6 @@ import ( "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/genesis" - //"github.com/ChainSafe/gossamer/lib/trie" "github.com/ChainSafe/chaindb" )