From e193a0c99004bab7fcf05762b91b7f244624a129 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Mon, 28 Mar 2022 14:10:08 -0400 Subject: [PATCH 01/93] wip: adding grandpa consensus messages digest --- dot/digest/digest_test.go | 181 +++++++++++++++++++++++++++++++++++--- 1 file changed, 171 insertions(+), 10 deletions(-) diff --git a/dot/digest/digest_test.go b/dot/digest/digest_test.go index 7d15248d6e..874f5dbc52 100644 --- a/dot/digest/digest_test.go +++ b/dot/digest/digest_test.go @@ -4,6 +4,7 @@ package digest import ( + "fmt" "testing" "time" @@ -11,19 +12,21 @@ import ( "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/internal/log" "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/crypto" "github.com/ChainSafe/gossamer/lib/crypto/ed25519" "github.com/ChainSafe/gossamer/lib/crypto/sr25519" "github.com/ChainSafe/gossamer/lib/genesis" "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/golang/mock/gomock" + "github.com/gtank/merlin" "github.com/stretchr/testify/require" ) //go:generate mockgen -destination=mock_telemetry_test.go -package $GOPACKAGE github.com/ChainSafe/gossamer/dot/telemetry Client -func newTestHandler(t *testing.T) *Handler { +func newTestHandler(t *testing.T) (*Handler, *state.Service) { testDatadirPath := t.TempDir() ctrl := gomock.NewController(t) @@ -49,11 +52,11 @@ func newTestHandler(t *testing.T) *Handler { dh, err := NewHandler(log.Critical, stateSrvc.Block, stateSrvc.Epoch, stateSrvc.Grandpa) require.NoError(t, err) - return dh + return dh, stateSrvc } func TestHandler_GrandpaScheduledChange(t *testing.T) { - handler := newTestHandler(t) + handler, _ := newTestHandler(t) handler.Start() defer handler.Stop() @@ -112,7 +115,7 @@ func TestHandler_GrandpaScheduledChange(t *testing.T) { } func TestHandler_GrandpaForcedChange(t *testing.T) { - handler := newTestHandler(t) + handler, _ := newTestHandler(t) handler.Start() defer handler.Stop() @@ -161,7 +164,7 @@ func TestHandler_GrandpaForcedChange(t *testing.T) { } func TestHandler_GrandpaPauseAndResume(t *testing.T) { - handler := newTestHandler(t) + handler, _ := newTestHandler(t) handler.Start() defer handler.Stop() @@ -224,7 +227,7 @@ func TestHandler_GrandpaPauseAndResume(t *testing.T) { } func TestNextGrandpaAuthorityChange_OneChange(t *testing.T) { - handler := newTestHandler(t) + handler, _ := newTestHandler(t) handler.Start() defer handler.Stop() @@ -264,7 +267,7 @@ func TestNextGrandpaAuthorityChange_OneChange(t *testing.T) { } func TestNextGrandpaAuthorityChange_MultipleChanges(t *testing.T) { - handler := newTestHandler(t) + handler, _ := newTestHandler(t) handler.Start() defer handler.Stop() @@ -337,7 +340,7 @@ func TestNextGrandpaAuthorityChange_MultipleChanges(t *testing.T) { } func TestHandler_HandleBABEOnDisabled(t *testing.T) { - handler := newTestHandler(t) + handler, _ := newTestHandler(t) header := &types.Header{ Number: 1, } @@ -384,7 +387,7 @@ func createHeaderWithPreDigest(t *testing.T, slotNumber uint64) *types.Header { func TestHandler_HandleNextEpochData(t *testing.T) { expData := common.MustHexToBytes("0x0108d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4801000000000000004d58630000000000000000000000000000000000000000000000000000000000") //nolint:lll - handler := newTestHandler(t) + handler, _ := newTestHandler(t) handler.Start() defer handler.Stop() @@ -437,7 +440,7 @@ func TestHandler_HandleNextEpochData(t *testing.T) { } func TestHandler_HandleNextConfigData(t *testing.T) { - handler := newTestHandler(t) + handler, _ := newTestHandler(t) handler.Start() defer handler.Stop() @@ -471,3 +474,161 @@ func TestHandler_HandleNextConfigData(t *testing.T) { require.NoError(t, err) require.Equal(t, act.ToConfigData(), stored) } + +func TestGrandpaScheduledChanges(t *testing.T) { + keyring, err := keystore.NewSr25519Keyring() + require.NoError(t, err) + + keyPairs := []*sr25519.Keypair{ + keyring.KeyAlice, keyring.KeyBob, keyring.KeyCharlie, + keyring.KeyDave, keyring.KeyEve, keyring.KeyFerdie, + keyring.KeyGeorge, keyring.KeyHeather, keyring.KeyIan, + } + + authorities := make([]types.AuthorityRaw, len(keyPairs)) + for i, keyPair := range keyPairs { + authorities[i] = types.AuthorityRaw{ + Key: keyPair.Public().(*sr25519.PublicKey).AsBytes(), + } + } + + digestHandler, stateService := newTestHandler(t) + + forksGRANPDAScheduledChanges := []types.GrandpaScheduledChange{ + { + Auths: []types.GrandpaAuthoritiesRaw{ + { + Key: authorities[0].Key, + ID: 0, + }, + { + Key: authorities[1].Key, + ID: 1, + }, + }, + Delay: 1, + }, + { + Auths: []types.GrandpaAuthoritiesRaw{ + { + Key: authorities[3].Key, + ID: 3, + }, + { + Key: authorities[4].Key, + ID: 4, + }, + }, + Delay: 1, + }, + { + Auths: []types.GrandpaAuthoritiesRaw{ + { + Key: authorities[6].Key, + ID: 6, + }, + { + Key: authorities[7].Key, + ID: 7, + }, + }, + Delay: 1, + }, + } + + genesisHeader, err := stateService.Block.BestBlockHeader() + require.NoError(t, err) + + forkA := issueBlocksWithGRANDPAScheduledChanges(t, keyring.KeyAlice, digestHandler, stateService, + genesisHeader, forksGRANPDAScheduledChanges[0], 2, 3) + forkB := issueBlocksWithGRANDPAScheduledChanges(t, keyring.KeyBob, digestHandler, stateService, + forkA[1], forksGRANPDAScheduledChanges[1], 1, 3) + forkC := issueBlocksWithGRANDPAScheduledChanges(t, keyring.KeyCharlie, digestHandler, stateService, + forkA[1], forksGRANPDAScheduledChanges[2], 1, 3) + + for _, fork := range forkA { + fmt.Printf("%d - %s\n", fork.Number, fork.Hash()) + } + + for _, fork := range forkB { + fmt.Printf("%d - %s\n", fork.Number, fork.Hash()) + } + + for _, fork := range forkC { + fmt.Printf("%d - %s\n", fork.Number, fork.Hash()) + } + + // current authorities at scheduled changes + digestHandler.grandpaScheduledChange.auths + //stateService.Block.AddBlock() + + //handler.handleConsensusDigest() +} + +func issueBlocksWithGRANDPAScheduledChanges(t *testing.T, kp *sr25519.Keypair, dh *Handler, + stateSvc *state.Service, parentHeader *types.Header, + sc types.GrandpaScheduledChange, atBlock int, size int) (headers []*types.Header) { + t.Helper() + + transcript := merlin.NewTranscript("BABE") //string(types.BabeEngineID[:]) + crypto.AppendUint64(transcript, []byte("slot number"), 1) + crypto.AppendUint64(transcript, []byte("current epoch"), 1) + transcript.AppendMessage([]byte("chain randomness"), []byte{}) + + output, proof, err := kp.VrfSign(transcript) + require.NoError(t, err) + + babePrimaryPreDigest := types.BabePrimaryPreDigest{ + SlotNumber: 1, + VRFOutput: output, + VRFProof: proof, + } + + preRuntimeDigest, err := babePrimaryPreDigest.ToPreRuntimeDigest() + require.NoError(t, err) + + digest := types.NewDigest() + + // include the consensus in the block being produced + if parentHeader.Number+1 == uint(atBlock) { + grandpaConsensusDigest := types.NewGrandpaConsensusDigest() + err = grandpaConsensusDigest.Set(sc) + require.NoError(t, err) + + grandpaDigest, err := scale.Marshal(grandpaConsensusDigest) + require.NoError(t, err) + + consensusDigest := types.ConsensusDigest{ + ConsensusEngineID: types.GrandpaEngineID, + Data: grandpaDigest, + } + require.NoError(t, digest.Add(*preRuntimeDigest, consensusDigest)) + } else { + require.NoError(t, digest.Add(*preRuntimeDigest)) + } + + header := &types.Header{ + ParentHash: parentHeader.Hash(), + Number: parentHeader.Number + 1, + Digest: digest, + } + + block := &types.Block{ + Header: *header, + Body: *types.NewBody([]types.Extrinsic{}), + } + + err = stateSvc.Block.AddBlock(block) + require.NoError(t, err) + + dh.HandleDigests(header) + + if size <= 0 { + headers = append(headers, header) + return headers + } + + headers = append(headers, header) + headers = append(headers, issueBlocksWithGRANDPAScheduledChanges(t, kp, dh, stateSvc, header, sc, atBlock, size-1)...) + return headers +} From edad574d99c1223115df724cc9d066788a7d0830 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Tue, 29 Mar 2022 10:31:41 -0400 Subject: [PATCH 02/93] wip: fork tree --- dot/digest/digest.go | 1 + dot/digest/digest_test.go | 1 - dot/state/grandpa.go | 20 ++++++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/dot/digest/digest.go b/dot/digest/digest.go index 1e8794a34c..2a34c1b80a 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -300,6 +300,7 @@ func (h *Handler) handleScheduledChange(sc types.GrandpaScheduledChange, header if err != nil { return err } + h.logger.Debugf("setting GrandpaScheduledChange at block %d", header.Number+uint(sc.Delay)) return h.grandpaState.SetNextChange( diff --git a/dot/digest/digest_test.go b/dot/digest/digest_test.go index 874f5dbc52..fa2fdb9858 100644 --- a/dot/digest/digest_test.go +++ b/dot/digest/digest_test.go @@ -559,7 +559,6 @@ func TestGrandpaScheduledChanges(t *testing.T) { } // current authorities at scheduled changes - digestHandler.grandpaScheduledChange.auths //stateService.Block.AddBlock() //handler.handleConsensusDigest() diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 0983e9614d..b78f35c707 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -6,6 +6,7 @@ package state import ( "encoding/binary" "errors" + "sync" "github.com/ChainSafe/chaindb" "github.com/ChainSafe/gossamer/dot/types" @@ -23,9 +24,26 @@ var ( currentSetIDKey = []byte("setID") ) +type GrandpaChanges interface { + types.GrandpaScheduledChange | + types.GrandpaForcedChange | + types.GrandpaOnDisabled | + types.GrandpaPause | + types.GrandpaResume +} + +type ForkLinkedList struct { + Hash common.Hash + ConsensusMessage scale.VaryingDataType + Next *ForkLinkedList +} + // GrandpaState tracks information related to grandpa type GrandpaState struct { db chaindb.Database + + forkLock sync.RWMutex + forks map[common.Hash]*ForkLinkedList } // NewGrandpaStateFromGenesis returns a new GrandpaState given the grandpa genesis authorities @@ -54,6 +72,8 @@ func NewGrandpaStateFromGenesis(db chaindb.Database, genesisAuthorities []types. return s, nil } +func (g *GrandpaState) Import() + // NewGrandpaState returns a new GrandpaState func NewGrandpaState(db chaindb.Database) (*GrandpaState, error) { return &GrandpaState{ From 5c9b3d0d36e6c51d9877c54f7aa9c0101dc42520 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Tue, 29 Mar 2022 19:51:51 -0400 Subject: [PATCH 03/93] chore: include grandpa state `ImportGrandpaChange` method --- dot/state/grandpa.go | 147 +++++++++++++++++++++++++++++++----- dot/state/grandpa_test.go | 133 ++++++++++++++++++++++++++++++-- dot/state/initialize.go | 2 +- dot/state/service.go | 2 +- lib/grandpa/grandpa_test.go | 2 +- 5 files changed, 258 insertions(+), 28 deletions(-) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index b78f35c707..9370b0aaf1 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -6,6 +6,7 @@ package state import ( "encoding/binary" "errors" + "fmt" "sync" "github.com/ChainSafe/chaindb" @@ -14,6 +15,16 @@ import ( "github.com/ChainSafe/gossamer/pkg/scale" ) +type grandpaChangeType byte + +const ( + grnpaScheduledChange grandpaChangeType = iota + grnpaForcedChange + grnpaOnDisabled + grnpaPause + grnpaResume +) + var ( genesisSetID = uint64(0) grandpaPrefix = "grandpa" @@ -24,33 +35,34 @@ var ( currentSetIDKey = []byte("setID") ) -type GrandpaChanges interface { - types.GrandpaScheduledChange | - types.GrandpaForcedChange | - types.GrandpaOnDisabled | - types.GrandpaPause | - types.GrandpaResume -} +var ( + ErrAlreadyHasForcedChanges = errors.New("already has a forced change") +) -type ForkLinkedList struct { - Hash common.Hash - ConsensusMessage scale.VaryingDataType - Next *ForkLinkedList +type ForkNode struct { + nodeType grandpaChangeType + header *types.Header + Change scale.VaryingDataType + Next *ForkNode } // GrandpaState tracks information related to grandpa type GrandpaState struct { - db chaindb.Database + db chaindb.Database + blockState *BlockState - forkLock sync.RWMutex - forks map[common.Hash]*ForkLinkedList + forksLock sync.RWMutex + forks map[common.Hash]*ForkNode } // NewGrandpaStateFromGenesis returns a new GrandpaState given the grandpa genesis authorities -func NewGrandpaStateFromGenesis(db chaindb.Database, genesisAuthorities []types.GrandpaVoter) (*GrandpaState, error) { +func NewGrandpaStateFromGenesis(db chaindb.Database, bs *BlockState, + genesisAuthorities []types.GrandpaVoter) (*GrandpaState, error) { grandpaDB := chaindb.NewTable(db, grandpaPrefix) s := &GrandpaState{ - db: grandpaDB, + db: grandpaDB, + blockState: bs, + forks: make(map[common.Hash]*ForkNode), } if err := s.setCurrentSetID(genesisSetID); err != nil { @@ -72,15 +84,110 @@ func NewGrandpaStateFromGenesis(db chaindb.Database, genesisAuthorities []types. return s, nil } -func (g *GrandpaState) Import() - // NewGrandpaState returns a new GrandpaState -func NewGrandpaState(db chaindb.Database) (*GrandpaState, error) { +func NewGrandpaState(db chaindb.Database, bs *BlockState) (*GrandpaState, error) { return &GrandpaState{ - db: chaindb.NewTable(db, grandpaPrefix), + db: chaindb.NewTable(db, grandpaPrefix), + blockState: bs, + forks: make(map[common.Hash]*ForkNode), }, nil } +func (gs *GrandpaState) ImportGrandpaChange(header *types.Header, change scale.VaryingDataType) error { + gs.forksLock.Lock() + defer gs.forksLock.Unlock() + + headerHash := header.Hash() + node := createNode(header, change) + + fmt.Printf("number: %d\n", header.Number) + + for inMemoryHash, linkedList := range gs.forks { + is, err := gs.blockState.IsDescendantOf(inMemoryHash, headerHash) + if err != nil { + return fmt.Errorf("cannot check ancestry while import grandpa change: %w", err) + } + + if !is { + // try the other way around as the block might not be in the right order + is, err = gs.blockState.IsDescendantOf(headerHash, inMemoryHash) + if err != nil { + return fmt.Errorf("cannot check ancestry while import grandpa change: %w", err) + } + + if !is { + continue + } + } + + alreadyHasForcedChanges := searchForForcedChanges(linkedList) + if alreadyHasForcedChanges { + return fmt.Errorf("cannot import block %s (%d): %w", + headerHash, header.Number, ErrAlreadyHasForcedChanges) + } + + node.Next = linkedList + gs.forks[inMemoryHash] = node + + keepDecreasingOrdered(node) + return nil + } + + gs.forks[headerHash] = node + return nil +} + +func searchForForcedChanges(node *ForkNode) (hasForcedChanges bool) { + if node == nil { + return false + } + + if node.nodeType == grnpaForcedChange { + return true + } + + return searchForForcedChanges(node.Next) +} + +// keepDecreasingOrdered receives the head of the fork linked list +// and check if the next node contains a number greater than the current head +// if so we switch places between them and do the same thing with the next nodes +// until we reach a node with a number less than our current node or the end of the +// list +func keepDecreasingOrdered(node *ForkNode) { + for node.Next != nil { + nextNode := node.Next + if node.header.Number > nextNode.header.Number { + break + } + + node.Next = nextNode.Next + nextNode.Next = node + } +} + +func createNode(header *types.Header, change scale.VaryingDataType) *ForkNode { + node := &ForkNode{ + Change: change, + header: header, + } + + switch change.Value().(type) { + case types.GrandpaScheduledChange: + node.nodeType = grnpaScheduledChange + case types.GrandpaForcedChange: + node.nodeType = grnpaForcedChange + case types.GrandpaOnDisabled: + node.nodeType = grnpaOnDisabled + case types.GrandpaPause: + node.nodeType = grnpaPause + case types.GrandpaResume: + node.nodeType = grnpaResume + } + + return node +} + func authoritiesKey(setID uint64) []byte { buf := make([]byte, 8) binary.LittleEndian.PutUint64(buf, setID) diff --git a/dot/state/grandpa_test.go b/dot/state/grandpa_test.go index a22add98fb..edaead39f9 100644 --- a/dot/state/grandpa_test.go +++ b/dot/state/grandpa_test.go @@ -4,11 +4,20 @@ package state import ( + "fmt" + "math/rand" "testing" + "time" + "github.com/ChainSafe/chaindb" "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/crypto" "github.com/ChainSafe/gossamer/lib/crypto/ed25519" + "github.com/ChainSafe/gossamer/lib/crypto/sr25519" "github.com/ChainSafe/gossamer/lib/keystore" + "github.com/ChainSafe/gossamer/lib/trie" + "github.com/golang/mock/gomock" + "github.com/gtank/merlin" "github.com/stretchr/testify/require" ) @@ -22,7 +31,7 @@ var ( func TestNewGrandpaStateFromGenesis(t *testing.T) { db := NewInMemoryDB(t) - gs, err := NewGrandpaStateFromGenesis(db, testAuths) + gs, err := NewGrandpaStateFromGenesis(db, nil, testAuths) require.NoError(t, err) currSetID, err := gs.GetCurrentSetID() @@ -40,7 +49,7 @@ func TestNewGrandpaStateFromGenesis(t *testing.T) { func TestGrandpaState_SetNextChange(t *testing.T) { db := NewInMemoryDB(t) - gs, err := NewGrandpaStateFromGenesis(db, testAuths) + gs, err := NewGrandpaStateFromGenesis(db, nil, testAuths) require.NoError(t, err) err = gs.SetNextChange(testAuths, 1) @@ -57,7 +66,7 @@ func TestGrandpaState_SetNextChange(t *testing.T) { func TestGrandpaState_IncrementSetID(t *testing.T) { db := NewInMemoryDB(t) - gs, err := NewGrandpaStateFromGenesis(db, testAuths) + gs, err := NewGrandpaStateFromGenesis(db, nil, testAuths) require.NoError(t, err) err = gs.IncrementSetID() @@ -70,7 +79,7 @@ func TestGrandpaState_IncrementSetID(t *testing.T) { func TestGrandpaState_GetSetIDByBlockNumber(t *testing.T) { db := NewInMemoryDB(t) - gs, err := NewGrandpaStateFromGenesis(db, testAuths) + gs, err := NewGrandpaStateFromGenesis(db, nil, testAuths) require.NoError(t, err) err = gs.SetNextChange(testAuths, 100) @@ -102,7 +111,7 @@ func TestGrandpaState_GetSetIDByBlockNumber(t *testing.T) { func TestGrandpaState_LatestRound(t *testing.T) { db := NewInMemoryDB(t) - gs, err := NewGrandpaStateFromGenesis(db, testAuths) + gs, err := NewGrandpaStateFromGenesis(db, nil, testAuths) require.NoError(t, err) r, err := gs.GetLatestRound() @@ -116,3 +125,117 @@ func TestGrandpaState_LatestRound(t *testing.T) { require.NoError(t, err) require.Equal(t, uint64(99), r) } + +func testBlockState(t *testing.T, db chaindb.Database) *BlockState { + ctrl := gomock.NewController(t) + telemetryMock := NewMockClient(ctrl) + telemetryMock.EXPECT().SendMessage(gomock.Any()).AnyTimes() + header := testGenesisHeader + + bs, err := NewBlockStateFromGenesis(db, newTriesEmpty(), header, telemetryMock) + require.NoError(t, err) + + // loads in-memory tries with genesis state root, should be deleted + // after another block is finalised + tr := trie.NewEmptyTrie() + err = tr.Load(bs.db, header.StateRoot) + require.NoError(t, err) + bs.tries.softSet(header.StateRoot, tr) + + return bs +} + +func TestImportGrandpaChangesKeepDecreasingOrdered(t *testing.T) { + keyring, err := keystore.NewSr25519Keyring() + require.NoError(t, err) + + db := NewInMemoryDB(t) + blockState := testBlockState(t, db) + + gs, err := NewGrandpaStateFromGenesis(db, blockState, nil) + require.NoError(t, err) + + scheduledChanges := types.GrandpaScheduledChange{} + forkChainA := issueBlocksWithGRANDPAScheduledChanges(t, keyring.KeyAlice, blockState, + testGenesisHeader, 3) + + forkChainA = shuffleHeaderSlice(forkChainA) + + for _, header := range forkChainA { + grandpaConsensusDigest := types.NewGrandpaConsensusDigest() + err := grandpaConsensusDigest.Set(scheduledChanges) + require.NoError(t, err) + + err = gs.ImportGrandpaChange(header, grandpaConsensusDigest) + require.NoError(t, err) + } + + require.Len(t, gs.forks, 1) + + forkAStartHash := forkChainA[0].Hash() + linkedList := gs.forks[forkAStartHash] + + for linkedList.Next != nil { + require.Greater(t, + linkedList.header.Number, linkedList.Next.header.Number) + linkedList = linkedList.Next + } + + fmt.Println() +} + +func issueBlocksWithGRANDPAScheduledChanges(t *testing.T, kp *sr25519.Keypair, + bs *BlockState, parentHeader *types.Header, size int) (headers []*types.Header) { + t.Helper() + + transcript := merlin.NewTranscript("BABE") //string(types.BabeEngineID[:]) + crypto.AppendUint64(transcript, []byte("slot number"), 1) + crypto.AppendUint64(transcript, []byte("current epoch"), 1) + transcript.AppendMessage([]byte("chain randomness"), []byte{}) + + output, proof, err := kp.VrfSign(transcript) + require.NoError(t, err) + + babePrimaryPreDigest := types.BabePrimaryPreDigest{ + SlotNumber: 1, + VRFOutput: output, + VRFProof: proof, + } + + preRuntimeDigest, err := babePrimaryPreDigest.ToPreRuntimeDigest() + require.NoError(t, err) + + digest := types.NewDigest() + + require.NoError(t, digest.Add(*preRuntimeDigest)) + header := &types.Header{ + ParentHash: parentHeader.Hash(), + Number: parentHeader.Number + 1, + Digest: digest, + } + + block := &types.Block{ + Header: *header, + Body: *types.NewBody([]types.Extrinsic{}), + } + + err = bs.AddBlock(block) + require.NoError(t, err) + + if size <= 0 { + headers = append(headers, header) + return headers + } + + headers = append(headers, header) + headers = append(headers, issueBlocksWithGRANDPAScheduledChanges(t, kp, bs, header, size-1)...) + return headers +} + +func shuffleHeaderSlice(headers []*types.Header) []*types.Header { + rand.Seed(time.Now().UnixNano()) + rand.Shuffle(len(headers), func(i, j int) { + headers[i], headers[j] = headers[j], headers[i] + }) + return headers +} diff --git a/dot/state/initialize.go b/dot/state/initialize.go index 3769303423..cd820a71a9 100644 --- a/dot/state/initialize.go +++ b/dot/state/initialize.go @@ -89,7 +89,7 @@ func (s *Service) Initialise(gen *genesis.Genesis, header *types.Header, t *trie return fmt.Errorf("failed to load grandpa authorities: %w", err) } - grandpaState, err := NewGrandpaStateFromGenesis(db, grandpaAuths) + grandpaState, err := NewGrandpaStateFromGenesis(db, blockState, grandpaAuths) if err != nil { return fmt.Errorf("failed to create grandpa state: %s", err) } diff --git a/dot/state/service.go b/dot/state/service.go index 86c5d76e42..5b513832cc 100644 --- a/dot/state/service.go +++ b/dot/state/service.go @@ -159,7 +159,7 @@ func (s *Service) Start() error { return fmt.Errorf("failed to create epoch state: %w", err) } - s.Grandpa, err = NewGrandpaState(s.db) + s.Grandpa, err = NewGrandpaState(s.db, s.Block) if err != nil { return fmt.Errorf("failed to create grandpa state: %w", err) } diff --git a/lib/grandpa/grandpa_test.go b/lib/grandpa/grandpa_test.go index da73202e8e..579a7c4005 100644 --- a/lib/grandpa/grandpa_test.go +++ b/lib/grandpa/grandpa_test.go @@ -73,7 +73,7 @@ func newTestState(t *testing.T) *state.Service { require.NoError(t, err) block.StoreRuntime(block.BestBlockHash(), rt) - grandpa, err := state.NewGrandpaStateFromGenesis(db, voters) + grandpa, err := state.NewGrandpaStateFromGenesis(db, nil, voters) require.NoError(t, err) return &state.Service{ From 8d64eac3caeb5b69e3f960fada88dd44ab2fbc56 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Fri, 1 Apr 2022 11:23:36 -0400 Subject: [PATCH 04/93] chore: wip using block tree --- dot/state/grandpa.go | 96 ------------------------ dot/state/grandpa_test.go | 150 ++++++++++++++++++-------------------- 2 files changed, 72 insertions(+), 174 deletions(-) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 9370b0aaf1..8298570c8b 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -6,7 +6,6 @@ package state import ( "encoding/binary" "errors" - "fmt" "sync" "github.com/ChainSafe/chaindb" @@ -93,101 +92,6 @@ func NewGrandpaState(db chaindb.Database, bs *BlockState) (*GrandpaState, error) }, nil } -func (gs *GrandpaState) ImportGrandpaChange(header *types.Header, change scale.VaryingDataType) error { - gs.forksLock.Lock() - defer gs.forksLock.Unlock() - - headerHash := header.Hash() - node := createNode(header, change) - - fmt.Printf("number: %d\n", header.Number) - - for inMemoryHash, linkedList := range gs.forks { - is, err := gs.blockState.IsDescendantOf(inMemoryHash, headerHash) - if err != nil { - return fmt.Errorf("cannot check ancestry while import grandpa change: %w", err) - } - - if !is { - // try the other way around as the block might not be in the right order - is, err = gs.blockState.IsDescendantOf(headerHash, inMemoryHash) - if err != nil { - return fmt.Errorf("cannot check ancestry while import grandpa change: %w", err) - } - - if !is { - continue - } - } - - alreadyHasForcedChanges := searchForForcedChanges(linkedList) - if alreadyHasForcedChanges { - return fmt.Errorf("cannot import block %s (%d): %w", - headerHash, header.Number, ErrAlreadyHasForcedChanges) - } - - node.Next = linkedList - gs.forks[inMemoryHash] = node - - keepDecreasingOrdered(node) - return nil - } - - gs.forks[headerHash] = node - return nil -} - -func searchForForcedChanges(node *ForkNode) (hasForcedChanges bool) { - if node == nil { - return false - } - - if node.nodeType == grnpaForcedChange { - return true - } - - return searchForForcedChanges(node.Next) -} - -// keepDecreasingOrdered receives the head of the fork linked list -// and check if the next node contains a number greater than the current head -// if so we switch places between them and do the same thing with the next nodes -// until we reach a node with a number less than our current node or the end of the -// list -func keepDecreasingOrdered(node *ForkNode) { - for node.Next != nil { - nextNode := node.Next - if node.header.Number > nextNode.header.Number { - break - } - - node.Next = nextNode.Next - nextNode.Next = node - } -} - -func createNode(header *types.Header, change scale.VaryingDataType) *ForkNode { - node := &ForkNode{ - Change: change, - header: header, - } - - switch change.Value().(type) { - case types.GrandpaScheduledChange: - node.nodeType = grnpaScheduledChange - case types.GrandpaForcedChange: - node.nodeType = grnpaForcedChange - case types.GrandpaOnDisabled: - node.nodeType = grnpaOnDisabled - case types.GrandpaPause: - node.nodeType = grnpaPause - case types.GrandpaResume: - node.nodeType = grnpaResume - } - - return node -} - func authoritiesKey(setID uint64) []byte { buf := make([]byte, 8) binary.LittleEndian.PutUint64(buf, setID) diff --git a/dot/state/grandpa_test.go b/dot/state/grandpa_test.go index edaead39f9..e3667722c0 100644 --- a/dot/state/grandpa_test.go +++ b/dot/state/grandpa_test.go @@ -4,20 +4,14 @@ package state import ( - "fmt" - "math/rand" "testing" - "time" "github.com/ChainSafe/chaindb" "github.com/ChainSafe/gossamer/dot/types" - "github.com/ChainSafe/gossamer/lib/crypto" "github.com/ChainSafe/gossamer/lib/crypto/ed25519" - "github.com/ChainSafe/gossamer/lib/crypto/sr25519" "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/lib/trie" "github.com/golang/mock/gomock" - "github.com/gtank/merlin" "github.com/stretchr/testify/require" ) @@ -145,97 +139,97 @@ func testBlockState(t *testing.T, db chaindb.Database) *BlockState { return bs } -func TestImportGrandpaChangesKeepDecreasingOrdered(t *testing.T) { - keyring, err := keystore.NewSr25519Keyring() - require.NoError(t, err) +// func TestImportGrandpaChangesKeepDecreasingOrdered(t *testing.T) { +// keyring, err := keystore.NewSr25519Keyring() +// require.NoError(t, err) - db := NewInMemoryDB(t) - blockState := testBlockState(t, db) +// db := NewInMemoryDB(t) +// blockState := testBlockState(t, db) - gs, err := NewGrandpaStateFromGenesis(db, blockState, nil) - require.NoError(t, err) +// gs, err := NewGrandpaStateFromGenesis(db, blockState, nil) +// require.NoError(t, err) - scheduledChanges := types.GrandpaScheduledChange{} - forkChainA := issueBlocksWithGRANDPAScheduledChanges(t, keyring.KeyAlice, blockState, - testGenesisHeader, 3) +// scheduledChanges := types.GrandpaScheduledChange{} +// forkChainA := issueBlocksWithGRANDPAScheduledChanges(t, keyring.KeyAlice, blockState, +// testGenesisHeader, 3) - forkChainA = shuffleHeaderSlice(forkChainA) +// forkChainA = shuffleHeaderSlice(forkChainA) - for _, header := range forkChainA { - grandpaConsensusDigest := types.NewGrandpaConsensusDigest() - err := grandpaConsensusDigest.Set(scheduledChanges) - require.NoError(t, err) +// for _, header := range forkChainA { +// grandpaConsensusDigest := types.NewGrandpaConsensusDigest() +// err := grandpaConsensusDigest.Set(scheduledChanges) +// require.NoError(t, err) - err = gs.ImportGrandpaChange(header, grandpaConsensusDigest) - require.NoError(t, err) - } +// err = gs.ImportGrandpaChange(header, grandpaConsensusDigest) +// require.NoError(t, err) +// } - require.Len(t, gs.forks, 1) +// require.Len(t, gs.forks, 1) - forkAStartHash := forkChainA[0].Hash() - linkedList := gs.forks[forkAStartHash] +// forkAStartHash := forkChainA[0].Hash() +// linkedList := gs.forks[forkAStartHash] - for linkedList.Next != nil { - require.Greater(t, - linkedList.header.Number, linkedList.Next.header.Number) - linkedList = linkedList.Next - } +// for linkedList.Next != nil { +// require.Greater(t, +// linkedList.header.Number, linkedList.Next.header.Number) +// linkedList = linkedList.Next +// } - fmt.Println() -} +// fmt.Println() +// } -func issueBlocksWithGRANDPAScheduledChanges(t *testing.T, kp *sr25519.Keypair, - bs *BlockState, parentHeader *types.Header, size int) (headers []*types.Header) { - t.Helper() +// func issueBlocksWithGRANDPAScheduledChanges(t *testing.T, kp *sr25519.Keypair, +// bs *BlockState, parentHeader *types.Header, size int) (headers []*types.Header) { +// t.Helper() - transcript := merlin.NewTranscript("BABE") //string(types.BabeEngineID[:]) - crypto.AppendUint64(transcript, []byte("slot number"), 1) - crypto.AppendUint64(transcript, []byte("current epoch"), 1) - transcript.AppendMessage([]byte("chain randomness"), []byte{}) +// transcript := merlin.NewTranscript("BABE") //string(types.BabeEngineID[:]) +// crypto.AppendUint64(transcript, []byte("slot number"), 1) +// crypto.AppendUint64(transcript, []byte("current epoch"), 1) +// transcript.AppendMessage([]byte("chain randomness"), []byte{}) - output, proof, err := kp.VrfSign(transcript) - require.NoError(t, err) +// output, proof, err := kp.VrfSign(transcript) +// require.NoError(t, err) - babePrimaryPreDigest := types.BabePrimaryPreDigest{ - SlotNumber: 1, - VRFOutput: output, - VRFProof: proof, - } +// babePrimaryPreDigest := types.BabePrimaryPreDigest{ +// SlotNumber: 1, +// VRFOutput: output, +// VRFProof: proof, +// } - preRuntimeDigest, err := babePrimaryPreDigest.ToPreRuntimeDigest() - require.NoError(t, err) +// preRuntimeDigest, err := babePrimaryPreDigest.ToPreRuntimeDigest() +// require.NoError(t, err) - digest := types.NewDigest() +// digest := types.NewDigest() - require.NoError(t, digest.Add(*preRuntimeDigest)) - header := &types.Header{ - ParentHash: parentHeader.Hash(), - Number: parentHeader.Number + 1, - Digest: digest, - } +// require.NoError(t, digest.Add(*preRuntimeDigest)) +// header := &types.Header{ +// ParentHash: parentHeader.Hash(), +// Number: parentHeader.Number + 1, +// Digest: digest, +// } - block := &types.Block{ - Header: *header, - Body: *types.NewBody([]types.Extrinsic{}), - } +// block := &types.Block{ +// Header: *header, +// Body: *types.NewBody([]types.Extrinsic{}), +// } - err = bs.AddBlock(block) - require.NoError(t, err) +// err = bs.AddBlock(block) +// require.NoError(t, err) - if size <= 0 { - headers = append(headers, header) - return headers - } +// if size <= 0 { +// headers = append(headers, header) +// return headers +// } - headers = append(headers, header) - headers = append(headers, issueBlocksWithGRANDPAScheduledChanges(t, kp, bs, header, size-1)...) - return headers -} +// headers = append(headers, header) +// headers = append(headers, issueBlocksWithGRANDPAScheduledChanges(t, kp, bs, header, size-1)...) +// return headers +// } -func shuffleHeaderSlice(headers []*types.Header) []*types.Header { - rand.Seed(time.Now().UnixNano()) - rand.Shuffle(len(headers), func(i, j int) { - headers[i], headers[j] = headers[j], headers[i] - }) - return headers -} +// func shuffleHeaderSlice(headers []*types.Header) []*types.Header { +// rand.Seed(time.Now().UnixNano()) +// rand.Shuffle(len(headers), func(i, j int) { +// headers[i], headers[j] = headers[j], headers[i] +// }) +// return headers +// } From ed7407f16e7c4e4f5ba3742155e3e882e62d20f2 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Wed, 6 Apr 2022 16:43:14 -0400 Subject: [PATCH 05/93] chore: implementing change across forks on scheduled changes --- dot/digest/digest.go | 137 +--------- dot/digest/digest_test.go | 226 ++++++++-------- dot/digest/interface.go | 2 + dot/node.go | 2 +- dot/services.go | 21 +- dot/services_integration_test.go | 5 +- dot/state/grandpa.go | 445 +++++++++++++++++++++++++++++-- dot/state/service_test.go | 6 +- lib/grandpa/grandpa.go | 53 ++-- lib/grandpa/grandpa_test.go | 25 +- lib/grandpa/round_test.go | 19 +- lib/grandpa/state.go | 6 +- lib/grandpa/vote_message_test.go | 115 ++++---- 13 files changed, 656 insertions(+), 406 deletions(-) diff --git a/dot/digest/digest.go b/dot/digest/digest.go index f715902285..9a207fe347 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -94,30 +94,6 @@ func (h *Handler) Stop() error { return nil } -// NextGrandpaAuthorityChange returns the block number of the next upcoming grandpa authorities change. -// It returns 0 if no change is scheduled. -func (h *Handler) NextGrandpaAuthorityChange() (next uint) { - next = ^uint(0) - - if h.grandpaScheduledChange != nil { - next = h.grandpaScheduledChange.atBlock - } - - if h.grandpaForcedChange != nil && h.grandpaForcedChange.atBlock < next { - next = h.grandpaForcedChange.atBlock - } - - if h.grandpaPause != nil && h.grandpaPause.atBlock < next { - next = h.grandpaPause.atBlock - } - - if h.grandpaResume != nil && h.grandpaResume.atBlock < next { - next = h.grandpaResume.atBlock - } - - return next -} - // HandleDigests handles consensus digests for an imported block func (h *Handler) HandleDigests(header *types.Header) { for i, d := range header.Digest.Types { @@ -142,42 +118,23 @@ func (h *Handler) handleConsensusDigest(d *types.ConsensusDigest, header *types. if err != nil { return err } - err = h.handleGrandpaConsensusDigest(data, header) - if err != nil { - return err - } - return nil + + return h.grandpaState.AddPendingChange(header, data) case types.BabeEngineID: data := types.NewBabeConsensusDigest() err := scale.Unmarshal(d.Data, &data) if err != nil { return err } - err = h.handleBabeConsensusDigest(data, header) - if err != nil { - return err - } - return nil + + return h.handleBabeConsensusDigest(data, header) } return errors.New("unknown consensus engine ID") } func (h *Handler) handleGrandpaConsensusDigest(digest scale.VaryingDataType, header *types.Header) error { - switch val := digest.Value().(type) { - case types.GrandpaScheduledChange: - return h.handleScheduledChange(val, header) - case types.GrandpaForcedChange: - return h.handleForcedChange(val, header) - case types.GrandpaOnDisabled: - return nil // do nothing, as this is not implemented in substrate - case types.GrandpaPause: - return h.handlePause(val) - case types.GrandpaResume: - return h.handleResume(val) - } - - return errors.New("invalid consensus digest data") + return h.grandpaState.AddPendingChange(header, digest) } func (h *Handler) handleBabeConsensusDigest(digest scale.VaryingDataType, header *types.Header) error { @@ -197,7 +154,8 @@ func (h *Handler) handleBabeConsensusDigest(digest scale.VaryingDataType, header return nil case types.BABEOnDisabled: - return h.handleBABEOnDisabled(val, header) + h.logger.Debug("handling BABEOnDisabled") + return nil case types.NextConfigData: currEpoch, err := h.epochState.GetEpochForBlock(header) @@ -224,6 +182,7 @@ func (h *Handler) handleBlockImport(ctx context.Context) { } h.HandleDigests(&block.Header) + err := h.handleGrandpaChangesOnImport(block.Header.Number) if err != nil { h.logger.Errorf("failed to handle grandpa changes on block import: %s", err) @@ -328,69 +287,6 @@ func (h *Handler) handleGrandpaChangesOnFinalization(num uint) error { return nil } -func (h *Handler) handleScheduledChange(sc types.GrandpaScheduledChange, header *types.Header) error { - curr, err := h.blockState.BestBlockHeader() - if err != nil { - return err - } - - if h.grandpaScheduledChange != nil { - return nil - } - - h.logger.Debugf("handling GrandpaScheduledChange data: %v", sc) - - c, err := newGrandpaChange(sc.Auths, sc.Delay, curr.Number) - if err != nil { - return err - } - - h.grandpaScheduledChange = c - - auths, err := types.GrandpaAuthoritiesRawToAuthorities(sc.Auths) - if err != nil { - return err - } - - h.logger.Debugf("setting GrandpaScheduledChange at block %d", - header.Number+uint(sc.Delay)) - return h.grandpaState.SetNextChange( - types.NewGrandpaVotersFromAuthorities(auths), - header.Number+uint(sc.Delay), - ) -} - -func (h *Handler) handleForcedChange(fc types.GrandpaForcedChange, header *types.Header) error { - if header == nil { - return errors.New("header is nil") - } - - if h.grandpaForcedChange != nil { - return errors.New("already have forced change scheduled") - } - - h.logger.Debugf("handling GrandpaForcedChange with data %v", fc) - - c, err := newGrandpaChange(fc.Auths, fc.Delay, header.Number) - if err != nil { - return err - } - - h.grandpaForcedChange = c - - auths, err := types.GrandpaAuthoritiesRawToAuthorities(fc.Auths) - if err != nil { - return err - } - - h.logger.Debugf("setting GrandpaForcedChange at block %d", - header.Number+uint(fc.Delay)) - return h.grandpaState.SetNextChange( - types.NewGrandpaVotersFromAuthorities(auths), - header.Number+uint(fc.Delay), - ) -} - func (h *Handler) handlePause(p types.GrandpaPause) error { curr, err := h.blockState.BestBlockHeader() if err != nil { @@ -416,20 +312,3 @@ func (h *Handler) handleResume(r types.GrandpaResume) error { return h.grandpaState.SetNextResume(h.grandpaResume.atBlock) } - -func newGrandpaChange(raw []types.GrandpaAuthoritiesRaw, delay uint32, currBlock uint) (*grandpaChange, error) { - auths, err := types.GrandpaAuthoritiesRawToAuthorities(raw) - if err != nil { - return nil, err - } - - return &grandpaChange{ - auths: auths, - atBlock: currBlock + uint(delay), - }, nil -} - -func (h *Handler) handleBABEOnDisabled(_ types.BABEOnDisabled, _ *types.Header) error { - h.logger.Debug("handling BABEOnDisabled") - return nil -} diff --git a/dot/digest/digest_test.go b/dot/digest/digest_test.go index c819768644..296535bfc2 100644 --- a/dot/digest/digest_test.go +++ b/dot/digest/digest_test.go @@ -228,118 +228,120 @@ func TestHandler_GrandpaPauseAndResume(t *testing.T) { require.Equal(t, uint(r.Delay+p.Delay), nextResume) } -func TestNextGrandpaAuthorityChange_OneChange(t *testing.T) { - handler, _ := newTestHandler(t) - handler.Start() - defer handler.Stop() - - const block uint = 3 - sc := types.GrandpaScheduledChange{ - Auths: []types.GrandpaAuthoritiesRaw{}, - Delay: uint32(block), - } - - var digest = types.NewGrandpaConsensusDigest() - err := digest.Set(sc) - require.NoError(t, err) - - data, err := scale.Marshal(digest) - require.NoError(t, err) - - d := &types.ConsensusDigest{ - ConsensusEngineID: types.GrandpaEngineID, - Data: data, - } - header := &types.Header{ - Number: 1, - } - - err = handler.handleConsensusDigest(d, header) - require.NoError(t, err) - - next := handler.NextGrandpaAuthorityChange() - require.Equal(t, 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) { - handler, _ := newTestHandler(t) - handler.Start() - defer handler.Stop() - - kr, err := keystore.NewEd25519Keyring() - require.NoError(t, err) - - later := uint32(6) - sc := types.GrandpaScheduledChange{ - Auths: []types.GrandpaAuthoritiesRaw{}, - Delay: later, - } - - var digest = types.NewGrandpaConsensusDigest() - err = digest.Set(sc) - require.NoError(t, err) - - data, err := scale.Marshal(digest) - require.NoError(t, err) - - d := &types.ConsensusDigest{ - ConsensusEngineID: types.GrandpaEngineID, - Data: data, - } - - header := &types.Header{ - Number: 1, - } - - err = handler.handleConsensusDigest(d, header) - require.NoError(t, err) - - nextSetID := uint64(1) - auths, err := handler.grandpaState.(*state.GrandpaState).GetAuthorities(nextSetID) - require.NoError(t, err) - expected, err := types.NewGrandpaVotersFromAuthoritiesRaw(sc.Auths) - require.NoError(t, err) - require.Equal(t, expected, auths) - - const earlier uint = 4 - fc := types.GrandpaForcedChange{ - Auths: []types.GrandpaAuthoritiesRaw{ - {Key: kr.Alice().Public().(*ed25519.PublicKey).AsBytes(), ID: 0}, - }, - Delay: uint32(earlier), - } - - digest = types.NewGrandpaConsensusDigest() - err = digest.Set(fc) - require.NoError(t, err) - - data, err = scale.Marshal(digest) - require.NoError(t, err) - - d = &types.ConsensusDigest{ - ConsensusEngineID: types.GrandpaEngineID, - Data: data, - } - - err = handler.handleConsensusDigest(d, header) - require.NoError(t, err) - - next := handler.NextGrandpaAuthorityChange() - require.Equal(t, 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) -} +// TODO: move this test to dot/state/grandpa.go +// func TestNextGrandpaAuthorityChange_OneChange(t *testing.T) { +// handler, _ := newTestHandler(t) +// handler.Start() +// defer handler.Stop() + +// const block uint = 3 +// sc := types.GrandpaScheduledChange{ +// Auths: []types.GrandpaAuthoritiesRaw{}, +// Delay: uint32(block), +// } + +// var digest = types.NewGrandpaConsensusDigest() +// err := digest.Set(sc) +// require.NoError(t, err) + +// data, err := scale.Marshal(digest) +// require.NoError(t, err) + +// d := &types.ConsensusDigest{ +// ConsensusEngineID: types.GrandpaEngineID, +// Data: data, +// } +// header := &types.Header{ +// Number: 1, +// } + +// err = handler.handleConsensusDigest(d, header) +// require.NoError(t, err) + +// next := handler.NextGrandpaAuthorityChange() +// require.Equal(t, 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) +// } + +// TODO: move this test to dot/state/grandpa.go +// func TestNextGrandpaAuthorityChange_MultipleChanges(t *testing.T) { +// handler, _ := newTestHandler(t) +// handler.Start() +// defer handler.Stop() + +// kr, err := keystore.NewEd25519Keyring() +// require.NoError(t, err) + +// later := uint32(6) +// sc := types.GrandpaScheduledChange{ +// Auths: []types.GrandpaAuthoritiesRaw{}, +// Delay: later, +// } + +// var digest = types.NewGrandpaConsensusDigest() +// err = digest.Set(sc) +// require.NoError(t, err) + +// data, err := scale.Marshal(digest) +// require.NoError(t, err) + +// d := &types.ConsensusDigest{ +// ConsensusEngineID: types.GrandpaEngineID, +// Data: data, +// } + +// header := &types.Header{ +// Number: 1, +// } + +// err = handler.handleConsensusDigest(d, header) +// require.NoError(t, err) + +// nextSetID := uint64(1) +// auths, err := handler.grandpaState.(*state.GrandpaState).GetAuthorities(nextSetID) +// require.NoError(t, err) +// expected, err := types.NewGrandpaVotersFromAuthoritiesRaw(sc.Auths) +// require.NoError(t, err) +// require.Equal(t, expected, auths) + +// const earlier uint = 4 +// fc := types.GrandpaForcedChange{ +// Auths: []types.GrandpaAuthoritiesRaw{ +// {Key: kr.Alice().Public().(*ed25519.PublicKey).AsBytes(), ID: 0}, +// }, +// Delay: uint32(earlier), +// } + +// digest = types.NewGrandpaConsensusDigest() +// err = digest.Set(fc) +// require.NoError(t, err) + +// data, err = scale.Marshal(digest) +// require.NoError(t, err) + +// d = &types.ConsensusDigest{ +// ConsensusEngineID: types.GrandpaEngineID, +// Data: data, +// } + +// err = handler.handleConsensusDigest(d, header) +// require.NoError(t, err) + +// next := handler.NextGrandpaAuthorityChange() +// require.Equal(t, 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 TestHandler_HandleBABEOnDisabled(t *testing.T) { handler, _ := newTestHandler(t) diff --git a/dot/digest/interface.go b/dot/digest/interface.go index 22c2fda1e5..b7da78c3c4 100644 --- a/dot/digest/interface.go +++ b/dot/digest/interface.go @@ -7,6 +7,7 @@ import ( "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/grandpa" + "github.com/ChainSafe/gossamer/pkg/scale" ) // BlockState interface for block state methods @@ -37,4 +38,5 @@ type GrandpaState interface { SetNextPause(number uint) error SetNextResume(number uint) error GetCurrentSetID() (uint64, error) + AddPendingChange(header *types.Header, digest scale.VaryingDataType) error } diff --git a/dot/node.go b/dot/node.go index bb5344b063..cde2c06198 100644 --- a/dot/node.go +++ b/dot/node.go @@ -293,7 +293,7 @@ func NewNode(cfg *Config, ks *keystore.GlobalKeystore) (*Node, error) { } nodeSrvcs = append(nodeSrvcs, coreSrvc) - fg, err := createGRANDPAService(cfg, stateSrvc, dh, ks.Gran, networkSrvc, telemetryMailer) + fg, err := createGRANDPAService(cfg, stateSrvc, ks.Gran, networkSrvc, telemetryMailer) if err != nil { return nil, err } diff --git a/dot/services.go b/dot/services.go index 0cdb74a278..775c1b3ffb 100644 --- a/dot/services.go +++ b/dot/services.go @@ -382,8 +382,8 @@ func createSystemService(cfg *types.SystemInfo, stateSrvc *state.Service) (*syst } // createGRANDPAService creates a new GRANDPA service -func createGRANDPAService(cfg *Config, st *state.Service, dh *digest.Handler, - ks keystore.Keystore, net *network.Service, telemetryMailer telemetry.Client) (*grandpa.Service, error) { +func createGRANDPAService(cfg *Config, st *state.Service, ks keystore.Keystore, + net *network.Service, telemetryMailer telemetry.Client) (*grandpa.Service, error) { rt, err := st.Block.GetRuntime(nil) if err != nil { return nil, err @@ -406,15 +406,14 @@ func createGRANDPAService(cfg *Config, st *state.Service, dh *digest.Handler, } gsCfg := &grandpa.Config{ - LogLvl: cfg.Log.FinalityGadgetLvl, - BlockState: st.Block, - GrandpaState: st.Grandpa, - DigestHandler: dh, - Voters: voters, - Authority: cfg.Core.GrandpaAuthority, - Network: net, - Interval: cfg.Core.GrandpaInterval, - Telemetry: telemetryMailer, + LogLvl: cfg.Log.FinalityGadgetLvl, + BlockState: st.Block, + GrandpaState: st.Grandpa, + Voters: voters, + Authority: cfg.Core.GrandpaAuthority, + Network: net, + Interval: cfg.Core.GrandpaInterval, + Telemetry: telemetryMailer, } if cfg.Core.GrandpaAuthority { diff --git a/dot/services_integration_test.go b/dot/services_integration_test.go index c6511ecd6c..7ad49a13fc 100644 --- a/dot/services_integration_test.go +++ b/dot/services_integration_test.go @@ -233,10 +233,7 @@ func TestCreateGrandpaService(t *testing.T) { err = loadRuntime(cfg, ns, stateSrvc, ks, &network.Service{}) require.NoError(t, err) - dh, err := createDigestHandler(cfg.Log.DigestLvl, stateSrvc) - require.NoError(t, err) - - gs, err := createGRANDPAService(cfg, stateSrvc, dh, ks.Gran, &network.Service{}, nil) + gs, err := createGRANDPAService(cfg, stateSrvc, ks.Gran, &network.Service{}, nil) require.NoError(t, err) require.NotNil(t, gs) } diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index fffe481ec5..f09e45a063 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -7,6 +7,7 @@ import ( "encoding/binary" "errors" "fmt" + "sort" "sync" "github.com/ChainSafe/chaindb" @@ -15,16 +16,6 @@ import ( "github.com/ChainSafe/gossamer/pkg/scale" ) -type grandpaChangeType byte - -const ( - grnpaScheduledChange grandpaChangeType = iota - grnpaForcedChange - grnpaOnDisabled - grnpaPause - grnpaResume -) - var ( genesisSetID = uint64(0) grandpaPrefix = "grandpa" @@ -36,14 +27,79 @@ var ( ) var ( - ErrAlreadyHasForcedChanges = errors.New("already has a forced change") + ErrAlreadyHasForcedChanges = errors.New("already has a forced change") + ErrUnfinalizedAncestor = errors.New("ancestor with changes not applied") + ErrGetNextAuthorityChangeBlockNumber = errors.New("cannot get the next authority change block number") + ErrLowerThanBestFinalized = errors.New("current finalized is lower than best finalized header") ) -type ForkNode struct { - nodeType grandpaChangeType - header *types.Header - Change scale.VaryingDataType - Next *ForkNode +type isDescendantOfFunc func(parent, child common.Hash) (bool, error) + +type changeNode struct { + header *types.Header + change *pendingChange + + nodes []*changeNode +} + +func (c *changeNode) importScheduledChange(header *types.Header, pendingChange *pendingChange, isDescendantOf isDescendantOfFunc) (imported bool, err error) { + if c.header.Hash() == header.Hash() { + return false, errors.New("duplicate block hash while importing change") + } + + if header.Number <= c.header.Number { + return false, nil + } + + for _, childrenNodes := range c.nodes { + imported, err := childrenNodes.importScheduledChange(header, pendingChange, isDescendantOf) + if err != nil { + return false, fmt.Errorf("could not import change: %w", err) + } + + if imported { + return true, nil + } + } + + isDescendant, err := isDescendantOf(c.header.Hash(), header.Hash()) + if err != nil { + return false, fmt.Errorf("cannot define ancestry: %w", err) + } + + if !isDescendant { + return false, nil + } + + changeNode := &changeNode{header: header, change: pendingChange, nodes: []*changeNode{}} + c.nodes = append(c.nodes, changeNode) + return true, nil +} + +type pendingChange struct { + delay uint32 + nextAuthorities []types.Authority + announcingHeader *types.Header +} + +func (p pendingChange) String() string { + return fmt.Sprintf("announcing header: %s (%d), delay: %d, next authorities: %d", + p.announcingHeader.Hash(), p.announcingHeader.Number, p.delay, len(p.nextAuthorities)) +} + +func (p *pendingChange) effectiveNumber() uint { + return p.announcingHeader.Number + uint(p.delay) +} + +type orderedPendingChanges []*pendingChange + +func (o orderedPendingChanges) Len() int { return len(o) } +func (o orderedPendingChanges) Swap(i, j int) { o[i], o[j] = o[j], o[i] } + +// Less order by first effective number then by block number +func (o orderedPendingChanges) Less(i, j int) bool { + return o[i].effectiveNumber() < o[j].effectiveNumber() && + o[i].announcingHeader.Number < o[j].announcingHeader.Number } // GrandpaState tracks information related to grandpa @@ -52,7 +108,9 @@ type GrandpaState struct { blockState *BlockState forksLock sync.RWMutex - forks map[common.Hash]*ForkNode + + scheduledChanges []*changeNode + forcedChanges orderedPendingChanges } // NewGrandpaStateFromGenesis returns a new GrandpaState given the grandpa genesis authorities @@ -62,7 +120,6 @@ func NewGrandpaStateFromGenesis(db chaindb.Database, bs *BlockState, s := &GrandpaState{ db: grandpaDB, blockState: bs, - forks: make(map[common.Hash]*ForkNode), } if err := s.setCurrentSetID(genesisSetID); err != nil { @@ -77,7 +134,7 @@ func NewGrandpaStateFromGenesis(db chaindb.Database, bs *BlockState, return nil, err } - if err := s.setSetIDChangeAtBlock(genesisSetID, 0); err != nil { + if err := s.setChangeSetIDAtBlock(genesisSetID, 0); err != nil { return nil, err } @@ -89,10 +146,341 @@ func NewGrandpaState(db chaindb.Database, bs *BlockState) (*GrandpaState, error) return &GrandpaState{ db: chaindb.NewTable(db, grandpaPrefix), blockState: bs, - forks: make(map[common.Hash]*ForkNode), }, nil } +func (s *GrandpaState) AddPendingChange(header *types.Header, digest scale.VaryingDataType) error { + switch val := digest.Value().(type) { + case types.GrandpaScheduledChange: + return s.addScheduledChange(header, val) + case types.GrandpaForcedChange: + return s.addForcedChange(header, val) + case types.GrandpaOnDisabled: + return nil + case types.GrandpaPause: + return nil + case types.GrandpaResume: + return nil + default: + return fmt.Errorf("not supported digest") + } +} + +func (s *GrandpaState) addForcedChange(header *types.Header, fc types.GrandpaForcedChange) error { + headerHash := header.Hash() + + for _, change := range s.forcedChanges { + changeBlockHash := change.announcingHeader.Hash() + + if changeBlockHash == headerHash { + return errors.New("duplicated hash") + } + + isDescendant, err := s.blockState.IsDescendantOf(changeBlockHash, headerHash) + if err != nil { + return fmt.Errorf("cannot verify ancestry: %w", err) + } + + if isDescendant { + return errors.New("multiple forced changes") + } + } + + auths, err := types.GrandpaAuthoritiesRawToAuthorities(fc.Auths) + if err != nil { + return fmt.Errorf("cannot parser GRANPDA authorities to raw authorities: %w", err) + } + + pendingChange := &pendingChange{ + nextAuthorities: auths, + announcingHeader: header, + delay: fc.Delay, + } + + s.forcedChanges = append(s.forcedChanges, pendingChange) + sort.Sort(s.forcedChanges) + + return nil +} + +func (s *GrandpaState) addScheduledChange(header *types.Header, sc types.GrandpaScheduledChange) error { + auths, err := types.GrandpaAuthoritiesRawToAuthorities(sc.Auths) + if err != nil { + return fmt.Errorf("cannot parser GRANPDA authorities to raw authorities: %w", err) + } + + pendingChange := &pendingChange{ + nextAuthorities: auths, + announcingHeader: header, + delay: sc.Delay, + } + + return s.importScheduledChange(header, pendingChange) +} + +func (s *GrandpaState) importScheduledChange(header *types.Header, pendingChange *pendingChange) error { + highestFinalizedHeader, err := s.blockState.GetHighestFinalisedHeader() + if err != nil { + return fmt.Errorf("cannot get highest finalized header: %w", err) + } + + if header.Number <= highestFinalizedHeader.Number { + return errors.New("cannot import changes from blocks older then our highest finalized block") + } + + for _, root := range s.scheduledChanges { + imported, err := root.importScheduledChange(header, pendingChange, s.blockState.IsDescendantOf) + + if err != nil { + return fmt.Errorf("could not import change: %w", err) + } + + if imported { + return nil + } + } + + changeNode := &changeNode{header: header, change: pendingChange, nodes: []*changeNode{}} + s.scheduledChanges = append(s.scheduledChanges, changeNode) + return nil +} + +// finalizedScheduledChange iterates throught the scheduled change tree roots looking for the change node, which +// contains a lower or equal effective number, to apply. When we found that node we update the scheduled change tree roots +// with its children that belongs to the same finalized node branch. If we don't find such node we update the scheduled +// change tree roots with the change nodes that belongs to the same finalized node branch +func (s *GrandpaState) finalizedScheduledChange(finalizedHeader *types.Header) (apply bool, change *pendingChange, err error) { + bestFinalizedHeader, err := s.blockState.GetHighestFinalisedHeader() + if err != nil { + return false, nil, fmt.Errorf("cannot get highest finalised header: %w", err) + } + + if finalizedHeader.Number <= bestFinalizedHeader.Number { + return false, nil, ErrLowerThanBestFinalized + } + + effectiveLowerThanFinalized := func(change *pendingChange) bool { + return change.effectiveNumber() <= finalizedHeader.Number + } + + finalizedHash := finalizedHeader.Hash() + position := -1 + for idx, root := range s.scheduledChanges { + if !effectiveLowerThanFinalized(root.change) { + continue + } + + rootHash := root.header.Hash() + + // if the current root doesn't have the same hash + // neither the finalized header is descendant of the root then skip to the next root + if !finalizedHash.Equal(rootHash) { + isDescendant, err := s.blockState.IsDescendantOf(rootHash, finalizedHash) + if err != nil { + return false, nil, fmt.Errorf("cannot verify ancestry: %w", err) + } + + if !isDescendant { + continue + } + } + + // as the changes needs to be applied in order we need to check if our finalized + // header is in front of any children, if it is that means some previous change was not applied + for _, node := range root.nodes { + isDescendant, err := s.blockState.IsDescendantOf(node.header.Hash(), finalizedHash) + if err != nil { + return false, nil, fmt.Errorf("cannot verify ancestry: %w", err) + } + + if node.header.Number <= finalizedHeader.Number && isDescendant { + return false, nil, ErrUnfinalizedAncestor + } + } + + position = idx + break + } + + var changeToApply *pendingChange = nil + + if position != -1 { + changeNodeAtPosition := s.scheduledChanges[position] + changeToApply = changeNodeAtPosition.change + + s.scheduledChanges = make([]*changeNode, len(changeNodeAtPosition.nodes)) + copy(s.scheduledChanges, changeNodeAtPosition.nodes) + } + + changed, err := s.retainScheduledChangeRoots(finalizedHeader) + if err != nil { + return false, nil, fmt.Errorf("cannot retain the scheduled change roots: %w", err) + } + + apply = changeToApply != nil || changed + return apply, changeToApply, nil +} + +func (s *GrandpaState) retainScheduledChangeRoots(finalizedHeader *types.Header) (changed bool, err error) { + newScheduledChangeRoots := []*changeNode{} + finalizedHash := finalizedHeader.Hash() + + for _, root := range s.scheduledChanges { + rootBlockHash := root.header.Hash() + + isAncestor, err := s.blockState.IsDescendantOf(finalizedHash, rootBlockHash) + if err != nil { + return false, fmt.Errorf("cannot verify ancestry while ancestor: %w", err) + } + + isDescendant, err := s.blockState.IsDescendantOf(rootBlockHash, finalizedHash) + if err != nil { + return false, fmt.Errorf("cannot verify ancestry while descendant: %w", err) + } + + retain := root.header.Number > finalizedHeader.Number && isAncestor || + root.header.Number == finalizedHeader.Number && finalizedHash.Equal(rootBlockHash) || + isDescendant + + if retain { + newScheduledChangeRoots = append(newScheduledChangeRoots, root) + } else { + changed = true + } + } + + s.scheduledChanges = make([]*changeNode, len(newScheduledChangeRoots)) + copy(s.scheduledChanges, newScheduledChangeRoots) + return changed, nil +} + +// applyScheduledChange will check the schedules changes in order to find a root +// that is equals or behind the finalized number and will apply its authority set changes +func (s *GrandpaState) applyScheduledChange(finalizedHeader *types.Header) error { + changed, changeToApply, err := s.finalizedScheduledChange(finalizedHeader) + if err != nil { + return fmt.Errorf("cannot finalize scheduled change: %w", err) + } + + logger.Debugf("finalized scheduled change: changes: %v, change to apply: %s", changed, changeToApply) + if !changed { + return nil + } + + finalizedHash := finalizedHeader.Hash() + onBranchForcedChanges := []*pendingChange{} + + for _, forcedChange := range s.forcedChanges { + isDescendant, err := s.blockState.IsDescendantOf(finalizedHash, forcedChange.announcingHeader.Hash()) + if err != nil { + return fmt.Errorf("cannot verify ancestry while ancestor: %w", err) + } + + if forcedChange.effectiveNumber() > finalizedHeader.Number && isDescendant { + onBranchForcedChanges = append(onBranchForcedChanges, forcedChange) + } + } + + s.forcedChanges = make(orderedPendingChanges, len(onBranchForcedChanges)) + copy(s.forcedChanges, onBranchForcedChanges) + + if changeToApply != nil { + newSetID, err := s.IncrementSetID() + if err != nil { + return fmt.Errorf("cannot increment set id: %w", err) + } + + grandpaVotersAuthorities := types.NewGrandpaVotersFromAuthorities(changeToApply.nextAuthorities) + err = s.setAuthorities(newSetID, grandpaVotersAuthorities) + if err != nil { + return fmt.Errorf("cannot set authorities: %w", err) + } + + err = s.setChangeSetIDAtBlock(newSetID, changeToApply.effectiveNumber()) + if err != nil { + return fmt.Errorf("cannot set change set id at block") + } + + logger.Debugf("Applying authority set change scheduled at block #%d", + changeToApply.announcingHeader.Number) + + // TODO: add afg.applying_scheduled_authority_set_change telemetry info here + } + + return nil +} + +// forcedChangeOnChainOf walk through the forced change slice looking for +// a forced change that belong to the same branch as bestBlockHash parameter +func (s *GrandpaState) forcedChangeOnChainOf(bestBlockHash common.Hash) (change *pendingChange, err error) { + for _, forcedChange := range s.forcedChanges { + forcedChangeHeader := forcedChange.announcingHeader + + var isDescendant bool + isDescendant, err = s.blockState.IsDescendantOf( + forcedChangeHeader.Hash(), bestBlockHash) + + if err != nil { + return nil, fmt.Errorf("cannot verify ancestry: %w", err) + } + + if !isDescendant { + continue + } + + return forcedChange, nil + } + + return nil, nil +} + +// scheduledChangeOnChainOf walk only through the scheduled changes roots slice looking for +// a scheduled change that belong to the same branch as bestBlockHash parameter +func (s *GrandpaState) scheduledChangeOnChainOf(bestBlockHash common.Hash) (change *pendingChange, err error) { + + for _, scheduledChange := range s.scheduledChanges { + var isDescendant bool + isDescendant, err = s.blockState.IsDescendantOf( + scheduledChange.header.Hash(), bestBlockHash) + + if err != nil { + return nil, fmt.Errorf("cannot verify ancestry: %w", err) + } + + if !isDescendant { + continue + } + + return scheduledChange.change, nil + } + + return nil, nil +} + +// NextGrandpaAuthorityChange returns the block number of the next upcoming grandpa authorities change. +// It returns 0 if no change is scheduled. +func (s *GrandpaState) NextGrandpaAuthorityChange(bestBlockHash common.Hash) (blockNumber uint, err error) { + forcedChange, err := s.forcedChangeOnChainOf(bestBlockHash) + if err != nil { + return 0, fmt.Errorf("cannot get forced change: %w", err) + } + + scheduledChange, err := s.scheduledChangeOnChainOf(bestBlockHash) + if err != nil { + return 0, fmt.Errorf("cannot get scheduled change: %w", err) + } + + if forcedChange == nil && scheduledChange == nil { + return 0, ErrGetNextAuthorityChangeBlockNumber + } + + if forcedChange.announcingHeader.Number < scheduledChange.announcingHeader.Number { + return forcedChange.effectiveNumber(), nil + } + + return scheduledChange.effectiveNumber(), nil +} + func authoritiesKey(setID uint64) []byte { buf := make([]byte, 8) binary.LittleEndian.PutUint64(buf, setID) @@ -182,7 +570,7 @@ func (s *GrandpaState) SetNextChange(authorities []types.GrandpaVoter, number ui return err } - err = s.setSetIDChangeAtBlock(nextSetID, number) + err = s.setChangeSetIDAtBlock(nextSetID, number) if err != nil { return err } @@ -207,7 +595,7 @@ func (s *GrandpaState) IncrementSetID() (newSetID uint64, err error) { } // setSetIDChangeAtBlock sets a set ID change at a certain block -func (s *GrandpaState) setSetIDChangeAtBlock(setID uint64, number uint) error { +func (s *GrandpaState) setChangeSetIDAtBlock(setID uint64, number uint) error { return s.db.Put(setIDChangeKey(setID), common.UintToBytes(number)) } @@ -222,7 +610,7 @@ func (s *GrandpaState) GetSetIDChange(setID uint64) (blockNumber uint, err error } // GetSetIDByBlockNumber returns the set ID for a given block number -func (s *GrandpaState) GetSetIDByBlockNumber(num uint) (uint64, error) { +func (s *GrandpaState) GetSetIDByBlockNumber(blockNumber uint) (uint64, error) { curr, err := s.GetCurrentSetID() if err != nil { return 0, err @@ -236,8 +624,7 @@ func (s *GrandpaState) GetSetIDByBlockNumber(num uint) (uint64, error) { } curr = curr - 1 continue - } - if err != nil { + } else if err != nil { return 0, err } @@ -246,13 +633,13 @@ func (s *GrandpaState) GetSetIDByBlockNumber(num uint) (uint64, error) { return 0, err } - // if the given block number is greater or equal to the block number of the set ID change, - // return the current set ID - if num <= changeUpper && num > changeLower { + // if the given block number is in the range of changeLower < blockNumber <= changeUpper + // return the set id to the change lower + if blockNumber <= changeUpper && blockNumber > changeLower { return curr, nil } - if num > changeUpper { + if blockNumber > changeUpper { return curr + 1, nil } diff --git a/dot/state/service_test.go b/dot/state/service_test.go index 01507161dd..b43ab30c07 100644 --- a/dot/state/service_test.go +++ b/dot/state/service_test.go @@ -316,13 +316,13 @@ func TestService_Rewind(t *testing.T) { err = serv.Grandpa.setCurrentSetID(3) require.NoError(t, err) - err = serv.Grandpa.setSetIDChangeAtBlock(1, 5) + err = serv.Grandpa.setChangeSetIDAtBlock(1, 5) require.NoError(t, err) - err = serv.Grandpa.setSetIDChangeAtBlock(2, 8) + err = serv.Grandpa.setChangeSetIDAtBlock(2, 8) require.NoError(t, err) - err = serv.Grandpa.setSetIDChangeAtBlock(3, 10) + err = serv.Grandpa.setChangeSetIDAtBlock(3, 10) require.NoError(t, err) AddBlocksToState(t, serv.Block, 12, false) diff --git a/lib/grandpa/grandpa.go b/lib/grandpa/grandpa.go index ffc2a01d9c..f851c58d59 100644 --- a/lib/grandpa/grandpa.go +++ b/lib/grandpa/grandpa.go @@ -13,6 +13,7 @@ import ( "sync/atomic" "time" + "github.com/ChainSafe/gossamer/dot/state" "github.com/ChainSafe/gossamer/dot/telemetry" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/internal/log" @@ -46,7 +47,6 @@ type Service struct { cancel context.CancelFunc blockState BlockState grandpaState GrandpaState - digestHandler DigestHandler keypair *ed25519.Keypair // TODO: change to grandpa keystore (#1870) mapLock sync.Mutex chanLock sync.Mutex @@ -83,16 +83,15 @@ type Service struct { // Config represents a GRANDPA service configuration type Config struct { - LogLvl log.Level - BlockState BlockState - GrandpaState GrandpaState - DigestHandler DigestHandler - Network Network - Voters []Voter - Keypair *ed25519.Keypair - Authority bool - Interval time.Duration - Telemetry telemetry.Client + LogLvl log.Level + BlockState BlockState + GrandpaState GrandpaState + Network Network + Voters []Voter + Keypair *ed25519.Keypair + Authority bool + Interval time.Duration + Telemetry telemetry.Client } // NewService returns a new GRANDPA Service instance. @@ -105,10 +104,6 @@ func NewService(cfg *Config) (*Service, error) { return nil, ErrNilGrandpaState } - if cfg.DigestHandler == nil { - return nil, ErrNilDigestHandler - } - if cfg.Keypair == nil && cfg.Authority { return nil, ErrNilKeypair } @@ -157,7 +152,6 @@ func NewService(cfg *Config) (*Service, error) { state: NewState(cfg.Voters, setID, round), blockState: cfg.BlockState, grandpaState: cfg.GrandpaState, - digestHandler: cfg.DigestHandler, keypair: cfg.Keypair, authority: cfg.Authority, prevotes: new(sync.Map), @@ -689,6 +683,11 @@ func (s *Service) deleteVote(key ed25519.PublicKeyBytes, stage Subround) { func (s *Service) determinePreVote() (*Vote, error) { var vote *Vote + beastBlockHeader, err := s.blockState.BestBlockHeader() + if err != nil { + return nil, fmt.Errorf("cannot get best block header: %w", err) + } + // if we receive a vote message from the primary with a // block that's greater than or equal to the current pre-voted block // and greater than the best final candidate from the last round, we choose that. @@ -698,15 +697,16 @@ func (s *Service) determinePreVote() (*Vote, error) { if has && prm.Vote.Number >= uint32(s.head.Number) { vote = &prm.Vote } else { - header, err := s.blockState.BestBlockHeader() - if err != nil { - return nil, err - } + vote = NewVoteFromHeader(beastBlockHeader) + } - vote = NewVoteFromHeader(header) + nextChange, err := s.grandpaState.NextGrandpaAuthorityChange(beastBlockHeader.Hash()) + if errors.Is(err, state.ErrGetNextAuthorityChangeBlockNumber) { + return vote, nil + } else if err != nil { + return nil, fmt.Errorf("cannot get next grandpa authority change: %w", err) } - nextChange := s.digestHandler.NextGrandpaAuthorityChange() if uint(vote.Number) > nextChange { header, err := s.blockState.GetHeaderByNumber(nextChange) if err != nil { @@ -730,7 +730,14 @@ func (s *Service) determinePreCommit() (*Vote, error) { s.preVotedBlock[s.state.round] = &pvb s.mapLock.Unlock() - nextChange := s.digestHandler.NextGrandpaAuthorityChange() + bestBlockHash := s.blockState.BestBlockHash() + nextChange, err := s.grandpaState.NextGrandpaAuthorityChange(bestBlockHash) + if errors.Is(err, state.ErrGetNextAuthorityChangeBlockNumber) { + return &pvb, nil + } else if err != nil { + return nil, fmt.Errorf("cannot get next grandpa authority change: %w", err) + } + if uint(pvb.Number) > nextChange { header, err := s.blockState.GetHeaderByNumber(nextChange) if err != nil { diff --git a/lib/grandpa/grandpa_test.go b/lib/grandpa/grandpa_test.go index 171806c80e..6905755406 100644 --- a/lib/grandpa/grandpa_test.go +++ b/lib/grandpa/grandpa_test.go @@ -22,8 +22,6 @@ import ( "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" - - "github.com/ChainSafe/gossamer/lib/grandpa/mocks" ) // testGenesisHeader is a test block header @@ -38,12 +36,6 @@ var ( voters = newTestVoters() ) -func NewMockDigestHandler() *mocks.DigestHandler { - m := new(mocks.DigestHandler) - m.On("NextGrandpaAuthorityChange").Return(uint(2 ^ 64 - 1)) - return m -} - //go:generate mockgen -destination=mock_telemetry_test.go -package $GOPACKAGE github.com/ChainSafe/gossamer/dot/telemetry Client func newTestState(t *testing.T) *state.Service { @@ -104,15 +96,14 @@ func newTestService(t *testing.T) (*Service, *state.Service) { telemetryMock.EXPECT().SendMessage(gomock.Any()).AnyTimes() cfg := &Config{ - BlockState: st.Block, - GrandpaState: st.Grandpa, - DigestHandler: NewMockDigestHandler(), - Voters: voters, - Keypair: kr.Alice().(*ed25519.Keypair), - Authority: true, - Network: net, - Interval: time.Second, - Telemetry: telemetryMock, + BlockState: st.Block, + GrandpaState: st.Grandpa, + Voters: voters, + Keypair: kr.Alice().(*ed25519.Keypair), + Authority: true, + Network: net, + Interval: time.Second, + Telemetry: telemetryMock, } gs, err := NewService(cfg) diff --git a/lib/grandpa/round_test.go b/lib/grandpa/round_test.go index d05508b5d6..2ed107a4cb 100644 --- a/lib/grandpa/round_test.go +++ b/lib/grandpa/round_test.go @@ -100,16 +100,15 @@ func setupGrandpa(t *testing.T, kp *ed25519.Keypair) ( SendMessage(gomock.Any()).AnyTimes() cfg := &Config{ - BlockState: st.Block, - GrandpaState: st.Grandpa, - DigestHandler: NewMockDigestHandler(), - Voters: voters, - Keypair: kp, - LogLvl: log.Info, - Authority: true, - Network: net, - Interval: time.Second, - Telemetry: telemetryMock, + BlockState: st.Block, + GrandpaState: st.Grandpa, + Voters: voters, + Keypair: kp, + LogLvl: log.Info, + Authority: true, + Network: net, + Interval: time.Second, + Telemetry: telemetryMock, } gs, err := NewService(cfg) diff --git a/lib/grandpa/state.go b/lib/grandpa/state.go index 63217856aa..426990ab38 100644 --- a/lib/grandpa/state.go +++ b/lib/grandpa/state.go @@ -50,11 +50,7 @@ type GrandpaState interface { //nolint:revive SetPrecommits(round, setID uint64, data []SignedVote) error GetPrevotes(round, setID uint64) ([]SignedVote, error) GetPrecommits(round, setID uint64) ([]SignedVote, error) -} - -// DigestHandler is the interface required by GRANDPA for the digest handler -type DigestHandler interface { // TODO: use GrandpaState instead (#1871) - NextGrandpaAuthorityChange() uint + NextGrandpaAuthorityChange(bestBlockHash common.Hash) (blockHeight uint, err error) } //go:generate mockery --name Network --structname Network --case underscore --keeptree diff --git a/lib/grandpa/vote_message_test.go b/lib/grandpa/vote_message_test.go index a6f9df79db..f433d0d1b6 100644 --- a/lib/grandpa/vote_message_test.go +++ b/lib/grandpa/vote_message_test.go @@ -22,13 +22,12 @@ func TestCheckForEquivocation_NoEquivocation(t *testing.T) { require.NoError(t, err) cfg := &Config{ - BlockState: st.Block, - GrandpaState: st.Grandpa, - DigestHandler: NewMockDigestHandler(), - Voters: voters, - Keypair: kr.Bob().(*ed25519.Keypair), - Network: net, - Interval: time.Second, + BlockState: st.Block, + GrandpaState: st.Grandpa, + Voters: voters, + Keypair: kr.Bob().(*ed25519.Keypair), + Network: net, + Interval: time.Second, } gs, err := NewService(cfg) @@ -57,13 +56,12 @@ func TestCheckForEquivocation_WithEquivocation(t *testing.T) { require.NoError(t, err) cfg := &Config{ - BlockState: st.Block, - GrandpaState: st.Grandpa, - DigestHandler: NewMockDigestHandler(), - Voters: voters, - Keypair: kr.Bob().(*ed25519.Keypair), - Network: net, - Interval: time.Second, + BlockState: st.Block, + GrandpaState: st.Grandpa, + Voters: voters, + Keypair: kr.Bob().(*ed25519.Keypair), + Network: net, + Interval: time.Second, } gs, err := NewService(cfg) @@ -103,13 +101,12 @@ func TestCheckForEquivocation_WithExistingEquivocation(t *testing.T) { require.NoError(t, err) cfg := &Config{ - BlockState: st.Block, - GrandpaState: st.Grandpa, - DigestHandler: NewMockDigestHandler(), - Voters: voters, - Keypair: kr.Bob().(*ed25519.Keypair), - Network: net, - Interval: time.Second, + BlockState: st.Block, + GrandpaState: st.Grandpa, + Voters: voters, + Keypair: kr.Bob().(*ed25519.Keypair), + Network: net, + Interval: time.Second, } gs, err := NewService(cfg) @@ -159,13 +156,12 @@ func TestValidateMessage_Valid(t *testing.T) { require.NoError(t, err) cfg := &Config{ - BlockState: st.Block, - GrandpaState: st.Grandpa, - DigestHandler: NewMockDigestHandler(), - Voters: voters, - Keypair: kr.Bob().(*ed25519.Keypair), - Network: net, - Interval: time.Second, + BlockState: st.Block, + GrandpaState: st.Grandpa, + Voters: voters, + Keypair: kr.Bob().(*ed25519.Keypair), + Network: net, + Interval: time.Second, } gs, err := NewService(cfg) @@ -193,13 +189,12 @@ func TestValidateMessage_InvalidSignature(t *testing.T) { require.NoError(t, err) cfg := &Config{ - BlockState: st.Block, - GrandpaState: st.Grandpa, - DigestHandler: NewMockDigestHandler(), - Voters: voters, - Keypair: kr.Bob().(*ed25519.Keypair), - Network: net, - Interval: time.Second, + BlockState: st.Block, + GrandpaState: st.Grandpa, + Voters: voters, + Keypair: kr.Bob().(*ed25519.Keypair), + Network: net, + Interval: time.Second, } gs, err := NewService(cfg) @@ -228,12 +223,11 @@ func TestValidateMessage_SetIDMismatch(t *testing.T) { require.NoError(t, err) cfg := &Config{ - BlockState: st.Block, - GrandpaState: st.Grandpa, - DigestHandler: NewMockDigestHandler(), - Keypair: kr.Bob().(*ed25519.Keypair), - Network: net, - Interval: time.Second, + BlockState: st.Block, + GrandpaState: st.Grandpa, + Keypair: kr.Bob().(*ed25519.Keypair), + Network: net, + Interval: time.Second, } gs, err := NewService(cfg) @@ -262,13 +256,12 @@ func TestValidateMessage_Equivocation(t *testing.T) { require.NoError(t, err) cfg := &Config{ - BlockState: st.Block, - GrandpaState: st.Grandpa, - DigestHandler: NewMockDigestHandler(), - Voters: voters, - Keypair: kr.Bob().(*ed25519.Keypair), - Network: net, - Interval: time.Second, + BlockState: st.Block, + GrandpaState: st.Grandpa, + Voters: voters, + Keypair: kr.Bob().(*ed25519.Keypair), + Network: net, + Interval: time.Second, } gs, err := NewService(cfg) @@ -306,13 +299,12 @@ func TestValidateMessage_BlockDoesNotExist(t *testing.T) { require.NoError(t, err) cfg := &Config{ - BlockState: st.Block, - GrandpaState: st.Grandpa, - DigestHandler: NewMockDigestHandler(), - Voters: voters, - Keypair: kr.Bob().(*ed25519.Keypair), - Network: net, - Interval: time.Second, + BlockState: st.Block, + GrandpaState: st.Grandpa, + Voters: voters, + Keypair: kr.Bob().(*ed25519.Keypair), + Network: net, + Interval: time.Second, } gs, err := NewService(cfg) @@ -341,13 +333,12 @@ func TestValidateMessage_IsNotDescendant(t *testing.T) { require.NoError(t, err) cfg := &Config{ - BlockState: st.Block, - GrandpaState: st.Grandpa, - DigestHandler: NewMockDigestHandler(), - Voters: voters, - Keypair: kr.Bob().(*ed25519.Keypair), - Network: net, - Interval: time.Second, + BlockState: st.Block, + GrandpaState: st.Grandpa, + Voters: voters, + Keypair: kr.Bob().(*ed25519.Keypair), + Network: net, + Interval: time.Second, } gs, err := NewService(cfg) From 18bf1d71fcc18bdd27d3f64203edceae0d32ac6e Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Thu, 7 Apr 2022 09:25:35 -0400 Subject: [PATCH 06/93] fix: verify if next epoch already contains some definition --- dot/digest/digest.go | 32 ++-- dot/digest/digest_test.go | 71 +++++++++ dot/digest/interface.go | 2 + dot/digest/mock_epoch_state_test.go | 147 ++++++++++++++++++ dot/rpc/modules/mocks/block_api.go | 2 +- dot/rpc/modules/mocks/block_finality_api.go | 2 +- dot/rpc/modules/mocks/block_producer_api.go | 2 +- dot/rpc/modules/mocks/core_api.go | 2 +- dot/rpc/modules/mocks/network_api.go | 2 +- dot/rpc/modules/mocks/rpcapi.go | 2 +- dot/rpc/modules/mocks/runtime_storage_api.go | 2 +- dot/rpc/modules/mocks/storage_api.go | 2 +- dot/rpc/modules/mocks/sync_state_api.go | 2 +- dot/rpc/modules/mocks/system_api.go | 2 +- .../modules/mocks/transaction_state_api.go | 2 +- dot/state/epoch.go | 24 +++ dot/state/epoch_test.go | 34 +++- dot/state/mock_observer.go | 2 +- dot/sync/mocks/babe_verifier.go | 2 +- dot/sync/mocks/block_import_handler.go | 2 +- dot/sync/mocks/block_state.go | 2 +- dot/sync/mocks/finality_gadget.go | 2 +- dot/sync/mocks/network.go | 2 +- lib/babe/mocks/block_import_handler.go | 2 +- lib/grandpa/mocks/network.go | 2 +- lib/runtime/mock_memory_test.go | 2 +- lib/runtime/mocks/instance.go | 2 +- lib/runtime/mocks/transaction_state.go | 2 +- lib/runtime/mocks/version.go | 2 +- lib/services/mocks/service.go | 2 +- 30 files changed, 315 insertions(+), 43 deletions(-) create mode 100644 dot/digest/mock_epoch_state_test.go diff --git a/dot/digest/digest.go b/dot/digest/digest.go index ad17d166d9..6bca24a510 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -17,8 +17,6 @@ import ( var ( _ services.Service = &Handler{} - - ErrDefineNextEpoch = errors.New("cannot define next epoch data and config") ) // Handler is used to handle consensus messages and relevant authority updates to BABE and GRANDPA @@ -267,23 +265,29 @@ func (h *Handler) persistBABEDigestsForNextEpoch(finalizedHeader *types.Header) } nextEpoch := currEpoch + 1 - err = h.epochState.FinalizeBABENextEpochData(nextEpoch) - if err != nil && !errors.Is(err, state.ErrEpochNotInMemory) { - return fmt.Errorf("cannot finalize babe next epoch data for block number %d (%s): %w", - finalizedHeader.Number, finalizedHeader.Hash(), err) + + appliedEpochData, appliedConfigData, err := h.epochState.AlreadyDefined(nextEpoch) + if err != nil { + return fmt.Errorf("cannot check if next epoch is already defined: %w", err) } - err = h.epochState.FinalizeBABENextConfigData(nextEpoch) - if err == nil { - return nil - } else if errors.Is(err, state.ErrEpochNotInMemory) { - return fmt.Errorf("%w: %s", ErrDefineNextEpoch, err) + if !appliedEpochData { + err = h.epochState.FinalizeBABENextEpochData(nextEpoch) + if err != nil && !errors.Is(err, state.ErrEpochNotInMemory) { + return fmt.Errorf("cannot finalize babe next epoch data for block number %d (%s): %w", + finalizedHeader.Number, finalizedHeader.Hash(), err) + } } - // the epoch state does not contains any information about the next epoch - return fmt.Errorf("cannot finalize babe next config data for block number %d (%s): %w", - finalizedHeader.Number, finalizedHeader.Hash(), err) + if !appliedConfigData { + err = h.epochState.FinalizeBABENextConfigData(nextEpoch) + if err != nil && !errors.Is(err, state.ErrEpochNotInMemory) { + return fmt.Errorf("cannot finalize babe next config data for block number %d (%s): %w", + finalizedHeader.Number, finalizedHeader.Hash(), err) + } + } + return nil } func (h *Handler) handleGrandpaChangesOnImport(num uint) error { diff --git a/dot/digest/digest_test.go b/dot/digest/digest_test.go index dcb0d8633b..53bd99b4c0 100644 --- a/dot/digest/digest_test.go +++ b/dot/digest/digest_test.go @@ -4,6 +4,7 @@ package digest import ( + "errors" "testing" "time" @@ -515,3 +516,73 @@ func TestHandler_HandleNextConfigData(t *testing.T) { require.NoError(t, err) require.Equal(t, act.ToConfigData(), stored) } + +func Test_persistBABEDigestsForNextEpoch(t *testing.T) { + type testStruct struct { + defineNextEpochData bool + defineNextConfigData bool + expectedErr error + alreadyDefinedFuncThrowError bool + } + + tests := map[string]testStruct{ + "epoch_data_already_defined": { + defineNextEpochData: true, + }, + "config_data_already_defined": { + defineNextConfigData: true, + }, + "both_already_defined": { + defineNextEpochData: true, + defineNextConfigData: true, + }, + "both_not_defined": {}, + "error_while_checking_already_defined": { + alreadyDefinedFuncThrowError: true, + expectedErr: errors.New( + "cannot check if next epoch is already defined: problems while calling function"), + }, + } + + for testName, tt := range tests { + tt := tt + t.Run(testName, func(t *testing.T) { + testFinalizedHeader := &types.Header{} + + ctrl := gomock.NewController(t) + epochStateMock := NewMockEpochState(ctrl) + + var currentEpoch uint64 = 1 + epochStateMock.EXPECT().GetEpochForBlock(testFinalizedHeader). + Return(currentEpoch, nil) + + if tt.alreadyDefinedFuncThrowError { + epochStateMock.EXPECT().AlreadyDefined(currentEpoch+1). + Return(false, false, errors.New("problems while calling function")) + } else { + epochStateMock.EXPECT().AlreadyDefined(currentEpoch+1). + Return(tt.defineNextEpochData, tt.defineNextConfigData, nil) + + if !tt.defineNextEpochData { + epochStateMock.EXPECT().FinalizeBABENextEpochData(currentEpoch + 1) + } + + if !tt.defineNextConfigData { + epochStateMock.EXPECT().FinalizeBABENextConfigData(currentEpoch + 1) + } + } + + digestHandler := &Handler{ + epochState: epochStateMock, + } + + err := digestHandler.persistBABEDigestsForNextEpoch(testFinalizedHeader) + if tt.expectedErr != nil { + require.Error(t, err) + require.EqualError(t, err, tt.expectedErr.Error()) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/dot/digest/interface.go b/dot/digest/interface.go index 22c2fda1e5..b4899396c2 100644 --- a/dot/digest/interface.go +++ b/dot/digest/interface.go @@ -18,6 +18,7 @@ type BlockState interface { FreeFinalisedNotifierChannel(ch chan *types.FinalisationInfo) } +//go:generate mockgen -destination=mock_epoch_state_test.go -package $GOPACKAGE . EpochState // EpochState is the interface for state.EpochState type EpochState interface { GetEpochForBlock(header *types.Header) (uint64, error) @@ -28,6 +29,7 @@ type EpochState interface { StoreBABENextConfigData(epoch uint64, hash common.Hash, nextEpochData types.NextConfigData) FinalizeBABENextEpochData(epoch uint64) error FinalizeBABENextConfigData(epoch uint64) error + AlreadyDefined(epoch uint64) (epochData bool, configData bool, err error) } // GrandpaState is the interface for the state.GrandpaState diff --git a/dot/digest/mock_epoch_state_test.go b/dot/digest/mock_epoch_state_test.go new file mode 100644 index 0000000000..9b2ea3c305 --- /dev/null +++ b/dot/digest/mock_epoch_state_test.go @@ -0,0 +1,147 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ChainSafe/gossamer/dot/digest (interfaces: EpochState) + +// Package digest is a generated GoMock package. +package digest + +import ( + reflect "reflect" + + types "github.com/ChainSafe/gossamer/dot/types" + common "github.com/ChainSafe/gossamer/lib/common" + gomock "github.com/golang/mock/gomock" +) + +// MockEpochState is a mock of EpochState interface. +type MockEpochState struct { + ctrl *gomock.Controller + recorder *MockEpochStateMockRecorder +} + +// MockEpochStateMockRecorder is the mock recorder for MockEpochState. +type MockEpochStateMockRecorder struct { + mock *MockEpochState +} + +// NewMockEpochState creates a new mock instance. +func NewMockEpochState(ctrl *gomock.Controller) *MockEpochState { + mock := &MockEpochState{ctrl: ctrl} + mock.recorder = &MockEpochStateMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockEpochState) EXPECT() *MockEpochStateMockRecorder { + return m.recorder +} + +// AlreadyDefined mocks base method. +func (m *MockEpochState) AlreadyDefined(arg0 uint64) (bool, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AlreadyDefined", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// AlreadyDefined indicates an expected call of AlreadyDefined. +func (mr *MockEpochStateMockRecorder) AlreadyDefined(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AlreadyDefined", reflect.TypeOf((*MockEpochState)(nil).AlreadyDefined), arg0) +} + +// FinalizeBABENextConfigData mocks base method. +func (m *MockEpochState) FinalizeBABENextConfigData(arg0 uint64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FinalizeBABENextConfigData", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// FinalizeBABENextConfigData indicates an expected call of FinalizeBABENextConfigData. +func (mr *MockEpochStateMockRecorder) FinalizeBABENextConfigData(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FinalizeBABENextConfigData", reflect.TypeOf((*MockEpochState)(nil).FinalizeBABENextConfigData), arg0) +} + +// FinalizeBABENextEpochData mocks base method. +func (m *MockEpochState) FinalizeBABENextEpochData(arg0 uint64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FinalizeBABENextEpochData", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// FinalizeBABENextEpochData indicates an expected call of FinalizeBABENextEpochData. +func (mr *MockEpochStateMockRecorder) FinalizeBABENextEpochData(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FinalizeBABENextEpochData", reflect.TypeOf((*MockEpochState)(nil).FinalizeBABENextEpochData), arg0) +} + +// GetEpochForBlock mocks base method. +func (m *MockEpochState) GetEpochForBlock(arg0 *types.Header) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetEpochForBlock", arg0) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetEpochForBlock indicates an expected call of GetEpochForBlock. +func (mr *MockEpochStateMockRecorder) GetEpochForBlock(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEpochForBlock", reflect.TypeOf((*MockEpochState)(nil).GetEpochForBlock), arg0) +} + +// SetConfigData mocks base method. +func (m *MockEpochState) SetConfigData(arg0 uint64, arg1 *types.ConfigData) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetConfigData", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetConfigData indicates an expected call of SetConfigData. +func (mr *MockEpochStateMockRecorder) SetConfigData(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetConfigData", reflect.TypeOf((*MockEpochState)(nil).SetConfigData), arg0, arg1) +} + +// SetEpochData mocks base method. +func (m *MockEpochState) SetEpochData(arg0 uint64, arg1 *types.EpochData) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetEpochData", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetEpochData indicates an expected call of SetEpochData. +func (mr *MockEpochStateMockRecorder) SetEpochData(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetEpochData", reflect.TypeOf((*MockEpochState)(nil).SetEpochData), arg0, arg1) +} + +// StoreBABENextConfigData mocks base method. +func (m *MockEpochState) StoreBABENextConfigData(arg0 uint64, arg1 common.Hash, arg2 types.NextConfigData) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "StoreBABENextConfigData", arg0, arg1, arg2) +} + +// StoreBABENextConfigData indicates an expected call of StoreBABENextConfigData. +func (mr *MockEpochStateMockRecorder) StoreBABENextConfigData(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StoreBABENextConfigData", reflect.TypeOf((*MockEpochState)(nil).StoreBABENextConfigData), arg0, arg1, arg2) +} + +// StoreBABENextEpochData mocks base method. +func (m *MockEpochState) StoreBABENextEpochData(arg0 uint64, arg1 common.Hash, arg2 types.NextEpochData) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "StoreBABENextEpochData", arg0, arg1, arg2) +} + +// StoreBABENextEpochData indicates an expected call of StoreBABENextEpochData. +func (mr *MockEpochStateMockRecorder) StoreBABENextEpochData(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StoreBABENextEpochData", reflect.TypeOf((*MockEpochState)(nil).StoreBABENextEpochData), arg0, arg1, arg2) +} diff --git a/dot/rpc/modules/mocks/block_api.go b/dot/rpc/modules/mocks/block_api.go index 8fae0eb002..e181efe6c1 100644 --- a/dot/rpc/modules/mocks/block_api.go +++ b/dot/rpc/modules/mocks/block_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.0. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/block_finality_api.go b/dot/rpc/modules/mocks/block_finality_api.go index cd163ce5c7..88f5603664 100644 --- a/dot/rpc/modules/mocks/block_finality_api.go +++ b/dot/rpc/modules/mocks/block_finality_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.0. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/block_producer_api.go b/dot/rpc/modules/mocks/block_producer_api.go index a18e2ace04..145a12cf05 100644 --- a/dot/rpc/modules/mocks/block_producer_api.go +++ b/dot/rpc/modules/mocks/block_producer_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.0. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/core_api.go b/dot/rpc/modules/mocks/core_api.go index baacb5b5b0..c0b7888174 100644 --- a/dot/rpc/modules/mocks/core_api.go +++ b/dot/rpc/modules/mocks/core_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.0. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/network_api.go b/dot/rpc/modules/mocks/network_api.go index 8ab4e28bec..7612263725 100644 --- a/dot/rpc/modules/mocks/network_api.go +++ b/dot/rpc/modules/mocks/network_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.0. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/rpcapi.go b/dot/rpc/modules/mocks/rpcapi.go index e5be5e8790..8a98eb7e7b 100644 --- a/dot/rpc/modules/mocks/rpcapi.go +++ b/dot/rpc/modules/mocks/rpcapi.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.0. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/runtime_storage_api.go b/dot/rpc/modules/mocks/runtime_storage_api.go index 0380c1cf82..cbeb4e6da5 100644 --- a/dot/rpc/modules/mocks/runtime_storage_api.go +++ b/dot/rpc/modules/mocks/runtime_storage_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.0. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/storage_api.go b/dot/rpc/modules/mocks/storage_api.go index 2b4557ce82..bfa38698ee 100644 --- a/dot/rpc/modules/mocks/storage_api.go +++ b/dot/rpc/modules/mocks/storage_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.0. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/sync_state_api.go b/dot/rpc/modules/mocks/sync_state_api.go index cfbc014ea4..36eb613373 100644 --- a/dot/rpc/modules/mocks/sync_state_api.go +++ b/dot/rpc/modules/mocks/sync_state_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.0. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/system_api.go b/dot/rpc/modules/mocks/system_api.go index f8f39b0c6a..d7d66e836a 100644 --- a/dot/rpc/modules/mocks/system_api.go +++ b/dot/rpc/modules/mocks/system_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.0. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/transaction_state_api.go b/dot/rpc/modules/mocks/transaction_state_api.go index d783820fa4..8bfc29a641 100644 --- a/dot/rpc/modules/mocks/transaction_state_api.go +++ b/dot/rpc/modules/mocks/transaction_state_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.0. DO NOT EDIT. package mocks diff --git a/dot/state/epoch.go b/dot/state/epoch.go index d77ae8fc32..1e97b78e97 100644 --- a/dot/state/epoch.go +++ b/dot/state/epoch.go @@ -512,6 +512,30 @@ func (s *EpochState) SkipVerify(header *types.Header) (bool, error) { return false, nil } +// AlreadyDefined will true for each returned value if it is already defined in the database for the given epoch +func (s *EpochState) AlreadyDefined(epoch uint64) (epochData bool, configData bool, err error) { + keys := [...][]byte{epochDataKey(epoch), configDataKey(epoch)} + applied := [...]bool{false, false} + + for idx, key := range keys { + enc, err := s.db.Get(key) + + if errors.Is(err, chaindb.ErrKeyNotFound) { + continue + } else if err != nil { + return false, false, fmt.Errorf("cannot retrieve %s from database: %w", string(key), err) + } + + if len(enc) == 0 { + continue + } + + applied[idx] = true + } + + return applied[0], applied[1], nil +} + // StoreBABENextEpochData stores the types.NextEpochData under epoch and hash keys func (s *EpochState) StoreBABENextEpochData(epoch uint64, hash common.Hash, nextEpochData types.NextEpochData) { s.nextEpochDataLock.Lock() diff --git a/dot/state/epoch_test.go b/dot/state/epoch_test.go index f3b4247890..1071f2a3ab 100644 --- a/dot/state/epoch_test.go +++ b/dot/state/epoch_test.go @@ -401,12 +401,24 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { err := epochState.blockState.db.Put(headerKey(tt.finalizeHash), []byte{}) require.NoError(t, err) + // before finalize next epoch data this epoch should not be defined + definedEpochData, definedConfigData, err := epochState.AlreadyDefined(tt.finalizeEpoch) + require.NoError(t, err) + require.False(t, definedEpochData) + require.False(t, definedConfigData) + err = epochState.FinalizeBABENextEpochData(tt.finalizeEpoch) if tt.expectErr != nil { require.ErrorIs(t, err, tt.expectErr) } else { require.NoError(t, err) + // after finalize next epoch data this epoch should not defined + definedEpochData, definedConfigData, err := epochState.AlreadyDefined(tt.finalizeEpoch) + require.NoError(t, err) + require.True(t, definedEpochData) + require.False(t, definedConfigData) + expected, err := expectedNextEpochData.ToEpochData() require.NoError(t, err) @@ -422,7 +434,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { } } -type inMemotyNextConfighData struct { +type inMemoryNextConfighData struct { epoch uint64 hashes []common.Hash nextConfigDatas []types.NextConfigData @@ -431,7 +443,7 @@ type inMemotyNextConfighData struct { func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { tests := map[string]struct { finalizeHash common.Hash - inMemoryEpoch []inMemotyNextConfighData + inMemoryEpoch []inMemoryNextConfighData finalizeEpoch uint64 expectErr error shouldRemainInMemory int @@ -440,7 +452,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { shouldRemainInMemory: 1, finalizeEpoch: 2, finalizeHash: common.MustHexToHash("0x68a27df5a52ff2251df2cc8368f7dcefb305a13bb3d89b65c8fb070f23877f2c"), - inMemoryEpoch: []inMemotyNextConfighData{ + inMemoryEpoch: []inMemoryNextConfighData{ { epoch: 1, hashes: []common.Hash{ @@ -511,7 +523,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { finalizeEpoch: 1, finalizeHash: common.Hash{}, // finalize when the hash does not exists expectErr: errHashNotPersisted, - inMemoryEpoch: []inMemotyNextConfighData{ + inMemoryEpoch: []inMemoryNextConfighData{ { epoch: 1, hashes: []common.Hash{ @@ -544,7 +556,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { finalizeEpoch: 3, // try to finalize a epoch that does not exists finalizeHash: common.Hash{}, expectErr: ErrEpochNotInMemory, - inMemoryEpoch: []inMemotyNextConfighData{ + inMemoryEpoch: []inMemoryNextConfighData{ { epoch: 1, hashes: []common.Hash{ @@ -591,12 +603,24 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { err := epochState.blockState.db.Put(headerKey(tt.finalizeHash), []byte{}) require.NoError(t, err) + // before finalize next config data this epoch should not be defined + definedEpochData, definedConfigData, err := epochState.AlreadyDefined(tt.finalizeEpoch) + require.NoError(t, err) + require.False(t, definedEpochData) + require.False(t, definedConfigData) + err = epochState.FinalizeBABENextConfigData(tt.finalizeEpoch) if tt.expectErr != nil { require.ErrorIs(t, err, tt.expectErr) } else { require.NoError(t, err) + // after finalize next config data this epoch should be defined + definedEpochData, definedConfigData, err = epochState.AlreadyDefined(tt.finalizeEpoch) + require.NoError(t, err) + require.False(t, definedEpochData) + require.True(t, definedConfigData) + gotConfigData, err := epochState.GetConfigData(tt.finalizeEpoch, nil) require.NoError(t, err) require.Equal(t, expectedConfigData.ToConfigData(), gotConfigData) diff --git a/dot/state/mock_observer.go b/dot/state/mock_observer.go index 6a0683586d..eacd834d94 100644 --- a/dot/state/mock_observer.go +++ b/dot/state/mock_observer.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.0. DO NOT EDIT. package state diff --git a/dot/sync/mocks/babe_verifier.go b/dot/sync/mocks/babe_verifier.go index 0be36b3ad8..5fbccebf5f 100644 --- a/dot/sync/mocks/babe_verifier.go +++ b/dot/sync/mocks/babe_verifier.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.0. DO NOT EDIT. package mocks diff --git a/dot/sync/mocks/block_import_handler.go b/dot/sync/mocks/block_import_handler.go index 10488e6f7a..707889776a 100644 --- a/dot/sync/mocks/block_import_handler.go +++ b/dot/sync/mocks/block_import_handler.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.0. DO NOT EDIT. package mocks diff --git a/dot/sync/mocks/block_state.go b/dot/sync/mocks/block_state.go index 367601d550..fe80df7afb 100644 --- a/dot/sync/mocks/block_state.go +++ b/dot/sync/mocks/block_state.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.0. DO NOT EDIT. package mocks diff --git a/dot/sync/mocks/finality_gadget.go b/dot/sync/mocks/finality_gadget.go index f3c1fc3e5e..6e0c24e5f5 100644 --- a/dot/sync/mocks/finality_gadget.go +++ b/dot/sync/mocks/finality_gadget.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.0. DO NOT EDIT. package mocks diff --git a/dot/sync/mocks/network.go b/dot/sync/mocks/network.go index d1b945bde6..e57141d40b 100644 --- a/dot/sync/mocks/network.go +++ b/dot/sync/mocks/network.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.0. DO NOT EDIT. package mocks diff --git a/lib/babe/mocks/block_import_handler.go b/lib/babe/mocks/block_import_handler.go index c803e1f65b..9ef4b5f84f 100644 --- a/lib/babe/mocks/block_import_handler.go +++ b/lib/babe/mocks/block_import_handler.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.0. DO NOT EDIT. package mocks diff --git a/lib/grandpa/mocks/network.go b/lib/grandpa/mocks/network.go index a1930740db..8756166777 100644 --- a/lib/grandpa/mocks/network.go +++ b/lib/grandpa/mocks/network.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.0. DO NOT EDIT. package mocks diff --git a/lib/runtime/mock_memory_test.go b/lib/runtime/mock_memory_test.go index 97374c51cc..43d9af4e69 100644 --- a/lib/runtime/mock_memory_test.go +++ b/lib/runtime/mock_memory_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.0. DO NOT EDIT. package runtime diff --git a/lib/runtime/mocks/instance.go b/lib/runtime/mocks/instance.go index 9188ba2349..01067cae59 100644 --- a/lib/runtime/mocks/instance.go +++ b/lib/runtime/mocks/instance.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.0. DO NOT EDIT. package mocks diff --git a/lib/runtime/mocks/transaction_state.go b/lib/runtime/mocks/transaction_state.go index 952ecfad4e..f6e3c1d492 100644 --- a/lib/runtime/mocks/transaction_state.go +++ b/lib/runtime/mocks/transaction_state.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.0. DO NOT EDIT. package mocks diff --git a/lib/runtime/mocks/version.go b/lib/runtime/mocks/version.go index b389875077..9844101e0d 100644 --- a/lib/runtime/mocks/version.go +++ b/lib/runtime/mocks/version.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.0. DO NOT EDIT. package mocks diff --git a/lib/services/mocks/service.go b/lib/services/mocks/service.go index 98752d2740..f57699b99a 100644 --- a/lib/services/mocks/service.go +++ b/lib/services/mocks/service.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.0. DO NOT EDIT. package mocks From 5ca05d0a18ba4bc32db6963ba5d73d16d79ae817 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Thu, 7 Apr 2022 09:38:19 -0400 Subject: [PATCH 07/93] chore: upgrade mockery version --- Makefile | 1 - dot/rpc/modules/mocks/block_api.go | 2 +- dot/rpc/modules/mocks/block_finality_api.go | 2 +- dot/rpc/modules/mocks/block_producer_api.go | 2 +- dot/rpc/modules/mocks/core_api.go | 2 +- dot/rpc/modules/mocks/network_api.go | 2 +- dot/rpc/modules/mocks/rpcapi.go | 2 +- dot/rpc/modules/mocks/runtime_storage_api.go | 2 +- dot/rpc/modules/mocks/storage_api.go | 2 +- dot/rpc/modules/mocks/sync_state_api.go | 2 +- dot/rpc/modules/mocks/system_api.go | 2 +- dot/rpc/modules/mocks/transaction_state_api.go | 2 +- dot/state/mock_observer.go | 2 +- dot/sync/mocks/babe_verifier.go | 2 +- dot/sync/mocks/block_import_handler.go | 2 +- dot/sync/mocks/block_state.go | 2 +- dot/sync/mocks/finality_gadget.go | 2 +- dot/sync/mocks/network.go | 2 +- lib/babe/mocks/block_import_handler.go | 2 +- lib/grandpa/mocks/network.go | 2 +- lib/runtime/mock_memory_test.go | 2 +- lib/runtime/mocks/instance.go | 2 +- lib/runtime/mocks/transaction_state.go | 2 +- lib/runtime/mocks/version.go | 2 +- lib/services/mocks/service.go | 2 +- 25 files changed, 24 insertions(+), 25 deletions(-) diff --git a/Makefile b/Makefile index 40efe95b21..fc3c79a99a 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,6 @@ lint: clean: rm -fr ./bin - proto: go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28 protoc -I=./dot/network/proto --go_out=./dot/network/proto dot/network/proto/api.v1.proto diff --git a/dot/rpc/modules/mocks/block_api.go b/dot/rpc/modules/mocks/block_api.go index e181efe6c1..8fae0eb002 100644 --- a/dot/rpc/modules/mocks/block_api.go +++ b/dot/rpc/modules/mocks/block_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.0. DO NOT EDIT. +// Code generated by mockery v2.10.4. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/block_finality_api.go b/dot/rpc/modules/mocks/block_finality_api.go index 88f5603664..cd163ce5c7 100644 --- a/dot/rpc/modules/mocks/block_finality_api.go +++ b/dot/rpc/modules/mocks/block_finality_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.0. DO NOT EDIT. +// Code generated by mockery v2.10.4. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/block_producer_api.go b/dot/rpc/modules/mocks/block_producer_api.go index 145a12cf05..a18e2ace04 100644 --- a/dot/rpc/modules/mocks/block_producer_api.go +++ b/dot/rpc/modules/mocks/block_producer_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.0. DO NOT EDIT. +// Code generated by mockery v2.10.4. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/core_api.go b/dot/rpc/modules/mocks/core_api.go index c0b7888174..baacb5b5b0 100644 --- a/dot/rpc/modules/mocks/core_api.go +++ b/dot/rpc/modules/mocks/core_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.0. DO NOT EDIT. +// Code generated by mockery v2.10.4. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/network_api.go b/dot/rpc/modules/mocks/network_api.go index 7612263725..8ab4e28bec 100644 --- a/dot/rpc/modules/mocks/network_api.go +++ b/dot/rpc/modules/mocks/network_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.0. DO NOT EDIT. +// Code generated by mockery v2.10.4. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/rpcapi.go b/dot/rpc/modules/mocks/rpcapi.go index 8a98eb7e7b..e5be5e8790 100644 --- a/dot/rpc/modules/mocks/rpcapi.go +++ b/dot/rpc/modules/mocks/rpcapi.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.0. DO NOT EDIT. +// Code generated by mockery v2.10.4. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/runtime_storage_api.go b/dot/rpc/modules/mocks/runtime_storage_api.go index cbeb4e6da5..0380c1cf82 100644 --- a/dot/rpc/modules/mocks/runtime_storage_api.go +++ b/dot/rpc/modules/mocks/runtime_storage_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.0. DO NOT EDIT. +// Code generated by mockery v2.10.4. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/storage_api.go b/dot/rpc/modules/mocks/storage_api.go index bfa38698ee..2b4557ce82 100644 --- a/dot/rpc/modules/mocks/storage_api.go +++ b/dot/rpc/modules/mocks/storage_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.0. DO NOT EDIT. +// Code generated by mockery v2.10.4. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/sync_state_api.go b/dot/rpc/modules/mocks/sync_state_api.go index 36eb613373..cfbc014ea4 100644 --- a/dot/rpc/modules/mocks/sync_state_api.go +++ b/dot/rpc/modules/mocks/sync_state_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.0. DO NOT EDIT. +// Code generated by mockery v2.10.4. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/system_api.go b/dot/rpc/modules/mocks/system_api.go index d7d66e836a..f8f39b0c6a 100644 --- a/dot/rpc/modules/mocks/system_api.go +++ b/dot/rpc/modules/mocks/system_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.0. DO NOT EDIT. +// Code generated by mockery v2.10.4. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/transaction_state_api.go b/dot/rpc/modules/mocks/transaction_state_api.go index 8bfc29a641..d783820fa4 100644 --- a/dot/rpc/modules/mocks/transaction_state_api.go +++ b/dot/rpc/modules/mocks/transaction_state_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.0. DO NOT EDIT. +// Code generated by mockery v2.10.4. DO NOT EDIT. package mocks diff --git a/dot/state/mock_observer.go b/dot/state/mock_observer.go index eacd834d94..6a0683586d 100644 --- a/dot/state/mock_observer.go +++ b/dot/state/mock_observer.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.0. DO NOT EDIT. +// Code generated by mockery v2.10.4. DO NOT EDIT. package state diff --git a/dot/sync/mocks/babe_verifier.go b/dot/sync/mocks/babe_verifier.go index 5fbccebf5f..0be36b3ad8 100644 --- a/dot/sync/mocks/babe_verifier.go +++ b/dot/sync/mocks/babe_verifier.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.0. DO NOT EDIT. +// Code generated by mockery v2.10.4. DO NOT EDIT. package mocks diff --git a/dot/sync/mocks/block_import_handler.go b/dot/sync/mocks/block_import_handler.go index 707889776a..10488e6f7a 100644 --- a/dot/sync/mocks/block_import_handler.go +++ b/dot/sync/mocks/block_import_handler.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.0. DO NOT EDIT. +// Code generated by mockery v2.10.4. DO NOT EDIT. package mocks diff --git a/dot/sync/mocks/block_state.go b/dot/sync/mocks/block_state.go index fe80df7afb..367601d550 100644 --- a/dot/sync/mocks/block_state.go +++ b/dot/sync/mocks/block_state.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.0. DO NOT EDIT. +// Code generated by mockery v2.10.4. DO NOT EDIT. package mocks diff --git a/dot/sync/mocks/finality_gadget.go b/dot/sync/mocks/finality_gadget.go index 6e0c24e5f5..f3c1fc3e5e 100644 --- a/dot/sync/mocks/finality_gadget.go +++ b/dot/sync/mocks/finality_gadget.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.0. DO NOT EDIT. +// Code generated by mockery v2.10.4. DO NOT EDIT. package mocks diff --git a/dot/sync/mocks/network.go b/dot/sync/mocks/network.go index e57141d40b..d1b945bde6 100644 --- a/dot/sync/mocks/network.go +++ b/dot/sync/mocks/network.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.0. DO NOT EDIT. +// Code generated by mockery v2.10.4. DO NOT EDIT. package mocks diff --git a/lib/babe/mocks/block_import_handler.go b/lib/babe/mocks/block_import_handler.go index 9ef4b5f84f..c803e1f65b 100644 --- a/lib/babe/mocks/block_import_handler.go +++ b/lib/babe/mocks/block_import_handler.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.0. DO NOT EDIT. +// Code generated by mockery v2.10.4. DO NOT EDIT. package mocks diff --git a/lib/grandpa/mocks/network.go b/lib/grandpa/mocks/network.go index 8756166777..a1930740db 100644 --- a/lib/grandpa/mocks/network.go +++ b/lib/grandpa/mocks/network.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.0. DO NOT EDIT. +// Code generated by mockery v2.10.4. DO NOT EDIT. package mocks diff --git a/lib/runtime/mock_memory_test.go b/lib/runtime/mock_memory_test.go index 43d9af4e69..97374c51cc 100644 --- a/lib/runtime/mock_memory_test.go +++ b/lib/runtime/mock_memory_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.0. DO NOT EDIT. +// Code generated by mockery v2.10.4. DO NOT EDIT. package runtime diff --git a/lib/runtime/mocks/instance.go b/lib/runtime/mocks/instance.go index 01067cae59..9188ba2349 100644 --- a/lib/runtime/mocks/instance.go +++ b/lib/runtime/mocks/instance.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.0. DO NOT EDIT. +// Code generated by mockery v2.10.4. DO NOT EDIT. package mocks diff --git a/lib/runtime/mocks/transaction_state.go b/lib/runtime/mocks/transaction_state.go index f6e3c1d492..952ecfad4e 100644 --- a/lib/runtime/mocks/transaction_state.go +++ b/lib/runtime/mocks/transaction_state.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.0. DO NOT EDIT. +// Code generated by mockery v2.10.4. DO NOT EDIT. package mocks diff --git a/lib/runtime/mocks/version.go b/lib/runtime/mocks/version.go index 9844101e0d..b389875077 100644 --- a/lib/runtime/mocks/version.go +++ b/lib/runtime/mocks/version.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.0. DO NOT EDIT. +// Code generated by mockery v2.10.4. DO NOT EDIT. package mocks diff --git a/lib/services/mocks/service.go b/lib/services/mocks/service.go index f57699b99a..98752d2740 100644 --- a/lib/services/mocks/service.go +++ b/lib/services/mocks/service.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.0. DO NOT EDIT. +// Code generated by mockery v2.10.4. DO NOT EDIT. package mocks From 44c3892331ee4bdf5bead4cbdfea66725ece2aa2 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Thu, 7 Apr 2022 09:45:26 -0400 Subject: [PATCH 08/93] chore: remove duplicated code with generics --- Makefile | 1 + dot/state/epoch_test.go | 54 ++++++++++++++++++----------------------- 2 files changed, 25 insertions(+), 30 deletions(-) diff --git a/Makefile b/Makefile index fc3c79a99a..40efe95b21 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,7 @@ lint: clean: rm -fr ./bin + proto: go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28 protoc -I=./dot/network/proto --go_out=./dot/network/proto dot/network/proto/api.v1.proto diff --git a/dot/state/epoch_test.go b/dot/state/epoch_test.go index 1071f2a3ab..acdd4d2dd5 100644 --- a/dot/state/epoch_test.go +++ b/dot/state/epoch_test.go @@ -226,10 +226,10 @@ func TestEpochState_GetEpochFromTime(t *testing.T) { require.Equal(t, uint64(99), epoch) } -type inMemoryNextEpochData struct { - epoch uint64 - hashes []common.Hash - nextEpochDatas []types.NextEpochData +type inMemoryBABEData[T any] struct { + epoch uint64 + hashes []common.Hash + Data []T } func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { @@ -254,7 +254,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { tests := map[string]struct { finalizeHash common.Hash - inMemoryEpoch []inMemoryNextEpochData + inMemoryEpoch []inMemoryBABEData[types.NextEpochData] finalizeEpoch uint64 expectErr error shouldRemainInMemory int @@ -263,7 +263,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { shouldRemainInMemory: 1, finalizeEpoch: 2, finalizeHash: common.MustHexToHash("0x68a27df5a52ff2251df2cc8368f7dcefb305a13bb3d89b65c8fb070f23877f2c"), - inMemoryEpoch: []inMemoryNextEpochData{ + inMemoryEpoch: []inMemoryBABEData[types.NextEpochData]{ { epoch: 1, hashes: []common.Hash{ @@ -271,7 +271,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"), common.MustHexToHash("0xc0096358534ec8d21d01d34b836eed476a1c343f8724fa2153dc0725ad797a90"), }, - nextEpochDatas: []types.NextEpochData{ + Data: []types.NextEpochData{ { Authorities: authorities[:3], Randomness: [32]byte{1}, @@ -293,7 +293,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { common.MustHexToHash("0xd380bee22de487a707cbda65dd9d4e2188f736908c42cf390c8919d4f7fc547c"), common.MustHexToHash("0x68a27df5a52ff2251df2cc8368f7dcefb305a13bb3d89b65c8fb070f23877f2c"), }, - nextEpochDatas: []types.NextEpochData{ + Data: []types.NextEpochData{ { Authorities: authorities[6:], Randomness: [32]byte{1}, @@ -313,7 +313,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { hashes: []common.Hash{ common.MustHexToHash("0xab5c9230a7dde8bb90a6728ba4a0165423294dac14336b1443f865b796ff682c"), }, - nextEpochDatas: []types.NextEpochData{ + Data: []types.NextEpochData{ { Authorities: authorities[6:], Randomness: [32]byte{1}, @@ -327,7 +327,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { finalizeEpoch: 1, finalizeHash: common.Hash{}, // finalize when the hash does not exists expectErr: errHashNotPersisted, - inMemoryEpoch: []inMemoryNextEpochData{ + inMemoryEpoch: []inMemoryBABEData[types.NextEpochData]{ { epoch: 1, hashes: []common.Hash{ @@ -335,7 +335,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"), common.MustHexToHash("0xc0096358534ec8d21d01d34b836eed476a1c343f8724fa2153dc0725ad797a90"), }, - nextEpochDatas: []types.NextEpochData{ + Data: []types.NextEpochData{ { Authorities: authorities[:3], Randomness: [32]byte{1}, @@ -357,7 +357,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { finalizeEpoch: 3, // try to finalize a epoch that does not exists finalizeHash: common.Hash{}, expectErr: ErrEpochNotInMemory, - inMemoryEpoch: []inMemoryNextEpochData{ + inMemoryEpoch: []inMemoryBABEData[types.NextEpochData]{ { epoch: 1, hashes: []common.Hash{ @@ -365,7 +365,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"), common.MustHexToHash("0xc0096358534ec8d21d01d34b836eed476a1c343f8724fa2153dc0725ad797a90"), }, - nextEpochDatas: []types.NextEpochData{ + Data: []types.NextEpochData{ { Authorities: authorities[:3], Randomness: [32]byte{1}, @@ -390,7 +390,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { for _, e := range tt.inMemoryEpoch { for i, hash := range e.hashes { - epochState.StoreBABENextEpochData(e.epoch, hash, e.nextEpochDatas[i]) + epochState.StoreBABENextEpochData(e.epoch, hash, e.Data[i]) } } @@ -434,16 +434,10 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { } } -type inMemoryNextConfighData struct { - epoch uint64 - hashes []common.Hash - nextConfigDatas []types.NextConfigData -} - func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { tests := map[string]struct { finalizeHash common.Hash - inMemoryEpoch []inMemoryNextConfighData + inMemoryEpoch []inMemoryBABEData[types.NextConfigData] finalizeEpoch uint64 expectErr error shouldRemainInMemory int @@ -452,7 +446,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { shouldRemainInMemory: 1, finalizeEpoch: 2, finalizeHash: common.MustHexToHash("0x68a27df5a52ff2251df2cc8368f7dcefb305a13bb3d89b65c8fb070f23877f2c"), - inMemoryEpoch: []inMemoryNextConfighData{ + inMemoryEpoch: []inMemoryBABEData[types.NextConfigData]{ { epoch: 1, hashes: []common.Hash{ @@ -460,7 +454,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"), common.MustHexToHash("0xc0096358534ec8d21d01d34b836eed476a1c343f8724fa2153dc0725ad797a90"), }, - nextConfigDatas: []types.NextConfigData{ + Data: []types.NextConfigData{ { C1: 1, C2: 2, @@ -485,7 +479,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { common.MustHexToHash("0xd380bee22de487a707cbda65dd9d4e2188f736908c42cf390c8919d4f7fc547c"), common.MustHexToHash("0x68a27df5a52ff2251df2cc8368f7dcefb305a13bb3d89b65c8fb070f23877f2c"), }, - nextConfigDatas: []types.NextConfigData{ + Data: []types.NextConfigData{ { C1: 1, C2: 2, @@ -508,7 +502,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { hashes: []common.Hash{ common.MustHexToHash("0xab5c9230a7dde8bb90a6728ba4a0165423294dac14336b1443f865b796ff682c"), }, - nextConfigDatas: []types.NextConfigData{ + Data: []types.NextConfigData{ { C1: 1, C2: 2, @@ -523,7 +517,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { finalizeEpoch: 1, finalizeHash: common.Hash{}, // finalize when the hash does not exists expectErr: errHashNotPersisted, - inMemoryEpoch: []inMemoryNextConfighData{ + inMemoryEpoch: []inMemoryBABEData[types.NextConfigData]{ { epoch: 1, hashes: []common.Hash{ @@ -531,7 +525,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"), common.MustHexToHash("0xc0096358534ec8d21d01d34b836eed476a1c343f8724fa2153dc0725ad797a90"), }, - nextConfigDatas: []types.NextConfigData{ + Data: []types.NextConfigData{ { C1: 1, C2: 2, @@ -556,7 +550,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { finalizeEpoch: 3, // try to finalize a epoch that does not exists finalizeHash: common.Hash{}, expectErr: ErrEpochNotInMemory, - inMemoryEpoch: []inMemoryNextConfighData{ + inMemoryEpoch: []inMemoryBABEData[types.NextConfigData]{ { epoch: 1, hashes: []common.Hash{ @@ -564,7 +558,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"), common.MustHexToHash("0xc0096358534ec8d21d01d34b836eed476a1c343f8724fa2153dc0725ad797a90"), }, - nextConfigDatas: []types.NextConfigData{ + Data: []types.NextConfigData{ { C1: 1, C2: 2, @@ -592,7 +586,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { for _, e := range tt.inMemoryEpoch { for i, hash := range e.hashes { - epochState.StoreBABENextConfigData(e.epoch, hash, e.nextConfigDatas[i]) + epochState.StoreBABENextConfigData(e.epoch, hash, e.Data[i]) } } From d69c694e1bdfe76047272cd3b399cf267108a111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ecl=C3=A9sio=20Junior?= Date: Thu, 7 Apr 2022 14:34:17 -0400 Subject: [PATCH 09/93] chore: improve test comment Co-authored-by: Quentin McGaw --- dot/state/epoch_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dot/state/epoch_test.go b/dot/state/epoch_test.go index acdd4d2dd5..f725b1e9fe 100644 --- a/dot/state/epoch_test.go +++ b/dot/state/epoch_test.go @@ -597,7 +597,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { err := epochState.blockState.db.Put(headerKey(tt.finalizeHash), []byte{}) require.NoError(t, err) - // before finalize next config data this epoch should not be defined + // before finalize next config data for this epoch should not be defined definedEpochData, definedConfigData, err := epochState.AlreadyDefined(tt.finalizeEpoch) require.NoError(t, err) require.False(t, definedEpochData) From 27bd2e815a2b2e6d68143fc037e3585e266b746b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ecl=C3=A9sio=20Junior?= Date: Thu, 7 Apr 2022 14:34:24 -0400 Subject: [PATCH 10/93] chore: improve test comment Co-authored-by: Quentin McGaw --- dot/state/epoch_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dot/state/epoch_test.go b/dot/state/epoch_test.go index f725b1e9fe..5ac2af93ad 100644 --- a/dot/state/epoch_test.go +++ b/dot/state/epoch_test.go @@ -609,7 +609,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { } else { require.NoError(t, err) - // after finalize next config data this epoch should be defined + // after finalize next config data for this epoch should be defined definedEpochData, definedConfigData, err = epochState.AlreadyDefined(tt.finalizeEpoch) require.NoError(t, err) require.False(t, definedEpochData) From bbddab4e7513bc8694a6f3054937e4bc5b3539fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ecl=C3=A9sio=20Junior?= Date: Thu, 7 Apr 2022 14:35:47 -0400 Subject: [PATCH 11/93] chore: improve test comment Co-authored-by: Quentin McGaw --- dot/state/epoch_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dot/state/epoch_test.go b/dot/state/epoch_test.go index 5ac2af93ad..519ee62a67 100644 --- a/dot/state/epoch_test.go +++ b/dot/state/epoch_test.go @@ -413,7 +413,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { } else { require.NoError(t, err) - // after finalize next epoch data this epoch should not defined + // after finalize next epoch data for this epoch should not defined definedEpochData, definedConfigData, err := epochState.AlreadyDefined(tt.finalizeEpoch) require.NoError(t, err) require.True(t, definedEpochData) From 71309cfce48f1b71756b20277893c037eea6e649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ecl=C3=A9sio=20Junior?= Date: Thu, 7 Apr 2022 14:36:02 -0400 Subject: [PATCH 12/93] chore: improve test comment Co-authored-by: Quentin McGaw --- dot/state/epoch_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dot/state/epoch_test.go b/dot/state/epoch_test.go index 519ee62a67..655a7d8fd0 100644 --- a/dot/state/epoch_test.go +++ b/dot/state/epoch_test.go @@ -401,7 +401,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { err := epochState.blockState.db.Put(headerKey(tt.finalizeHash), []byte{}) require.NoError(t, err) - // before finalize next epoch data this epoch should not be defined + // before finalize next epoch data for this epoch should not be defined definedEpochData, definedConfigData, err := epochState.AlreadyDefined(tt.finalizeEpoch) require.NoError(t, err) require.False(t, definedEpochData) From 58edb997550e8abfad71c3218a596cae1a40cc57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ecl=C3=A9sio=20Junior?= Date: Thu, 7 Apr 2022 14:37:17 -0400 Subject: [PATCH 13/93] chore: remove the go:generate from the `EpochState`'s comment Co-authored-by: Quentin McGaw --- dot/digest/interface.go | 1 + 1 file changed, 1 insertion(+) diff --git a/dot/digest/interface.go b/dot/digest/interface.go index b4899396c2..1ef2b561a8 100644 --- a/dot/digest/interface.go +++ b/dot/digest/interface.go @@ -19,6 +19,7 @@ type BlockState interface { } //go:generate mockgen -destination=mock_epoch_state_test.go -package $GOPACKAGE . EpochState + // EpochState is the interface for state.EpochState type EpochState interface { GetEpochForBlock(header *types.Header) (uint64, error) From 47c8836f265c970fbaa2bf3b7c64523cdb638f92 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Fri, 8 Apr 2022 09:50:49 -0400 Subject: [PATCH 14/93] chore: use functions to define mocks in the test cases --- dot/digest/digest_test.go | 102 +++++++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 34 deletions(-) diff --git a/dot/digest/digest_test.go b/dot/digest/digest_test.go index 53bd99b4c0..6e1800e3c7 100644 --- a/dot/digest/digest_test.go +++ b/dot/digest/digest_test.go @@ -518,27 +518,83 @@ func TestHandler_HandleNextConfigData(t *testing.T) { } func Test_persistBABEDigestsForNextEpoch(t *testing.T) { + const currentEpoch uint64 = 1 + type testStruct struct { - defineNextEpochData bool - defineNextConfigData bool - expectedErr error - alreadyDefinedFuncThrowError bool + expectedErr error + epochState func(ctrl *gomock.Controller) EpochState } tests := map[string]testStruct{ "epoch_data_already_defined": { - defineNextEpochData: true, + epochState: func(ctrl *gomock.Controller) EpochState { + epochStateMock := NewMockEpochState(ctrl) + + epochStateMock.EXPECT().GetEpochForBlock(&types.Header{}). + Return(currentEpoch, nil) + + epochStateMock.EXPECT().AlreadyDefined(currentEpoch+1). + Return(true, false, nil) + + epochStateMock.EXPECT().FinalizeBABENextConfigData(currentEpoch + 1) + return epochStateMock + }, }, "config_data_already_defined": { - defineNextConfigData: true, + epochState: func(ctrl *gomock.Controller) EpochState { + epochStateMock := NewMockEpochState(ctrl) + + epochStateMock.EXPECT().GetEpochForBlock(&types.Header{}). + Return(currentEpoch, nil) + + epochStateMock.EXPECT().AlreadyDefined(currentEpoch+1). + Return(false, true, nil) + + epochStateMock.EXPECT().FinalizeBABENextEpochData(currentEpoch + 1) + return epochStateMock + }, }, "both_already_defined": { - defineNextEpochData: true, - defineNextConfigData: true, + epochState: func(ctrl *gomock.Controller) EpochState { + epochStateMock := NewMockEpochState(ctrl) + + epochStateMock.EXPECT().GetEpochForBlock(&types.Header{}). + Return(currentEpoch, nil) + + epochStateMock.EXPECT().AlreadyDefined(currentEpoch+1). + Return(true, true, nil) + + return epochStateMock + }, + }, + "both_not_defined": { + epochState: func(ctrl *gomock.Controller) EpochState { + epochStateMock := NewMockEpochState(ctrl) + + epochStateMock.EXPECT().GetEpochForBlock(&types.Header{}). + Return(currentEpoch, nil) + + epochStateMock.EXPECT().AlreadyDefined(currentEpoch+1). + Return(false, false, nil) + + epochStateMock.EXPECT().FinalizeBABENextEpochData(currentEpoch + 1) + epochStateMock.EXPECT().FinalizeBABENextConfigData(currentEpoch + 1) + + return epochStateMock + }, }, - "both_not_defined": {}, "error_while_checking_already_defined": { - alreadyDefinedFuncThrowError: true, + epochState: func(ctrl *gomock.Controller) EpochState { + epochStateMock := NewMockEpochState(ctrl) + + epochStateMock.EXPECT().GetEpochForBlock(&types.Header{}). + Return(currentEpoch, nil) + + epochStateMock.EXPECT().AlreadyDefined(currentEpoch+1). + Return(false, false, errors.New("problems while calling function")) + + return epochStateMock + }, expectedErr: errors.New( "cannot check if next epoch is already defined: problems while calling function"), }, @@ -547,36 +603,14 @@ func Test_persistBABEDigestsForNextEpoch(t *testing.T) { for testName, tt := range tests { tt := tt t.Run(testName, func(t *testing.T) { - testFinalizedHeader := &types.Header{} - ctrl := gomock.NewController(t) - epochStateMock := NewMockEpochState(ctrl) - - var currentEpoch uint64 = 1 - epochStateMock.EXPECT().GetEpochForBlock(testFinalizedHeader). - Return(currentEpoch, nil) - - if tt.alreadyDefinedFuncThrowError { - epochStateMock.EXPECT().AlreadyDefined(currentEpoch+1). - Return(false, false, errors.New("problems while calling function")) - } else { - epochStateMock.EXPECT().AlreadyDefined(currentEpoch+1). - Return(tt.defineNextEpochData, tt.defineNextConfigData, nil) - - if !tt.defineNextEpochData { - epochStateMock.EXPECT().FinalizeBABENextEpochData(currentEpoch + 1) - } - - if !tt.defineNextConfigData { - epochStateMock.EXPECT().FinalizeBABENextConfigData(currentEpoch + 1) - } - } + epochStateMock := tt.epochState(ctrl) digestHandler := &Handler{ epochState: epochStateMock, } - err := digestHandler.persistBABEDigestsForNextEpoch(testFinalizedHeader) + err := digestHandler.persistBABEDigestsForNextEpoch(&types.Header{}) if tt.expectedErr != nil { require.Error(t, err) require.EqualError(t, err, tt.expectedErr.Error()) From a25734952a72b02185b78bcf6597de924df2ae2b Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Fri, 8 Apr 2022 11:51:06 -0400 Subject: [PATCH 15/93] chore: add more explanatory comments --- dot/state/grandpa.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index f09e45a063..738d14d302 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -321,6 +321,8 @@ func (s *GrandpaState) finalizedScheduledChange(finalizedHeader *types.Header) ( return apply, changeToApply, nil } +// retainScheduledChangeRoots keeps the child nodes from the applied standard change as the new roots of the +// s.scheduledChanges tree func (s *GrandpaState) retainScheduledChangeRoots(finalizedHeader *types.Header) (changed bool, err error) { newScheduledChangeRoots := []*changeNode{} finalizedHash := finalizedHeader.Hash() @@ -370,6 +372,8 @@ func (s *GrandpaState) applyScheduledChange(finalizedHeader *types.Header) error finalizedHash := finalizedHeader.Hash() onBranchForcedChanges := []*pendingChange{} + // we should keep the forced changes for later blocks that + // are descendant of the finalized block for _, forcedChange := range s.forcedChanges { isDescendant, err := s.blockState.IsDescendantOf(finalizedHash, forcedChange.announcingHeader.Hash()) if err != nil { From 088edcd2f496184917e3db7827ff3b9ec5a4b040 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Fri, 8 Apr 2022 13:45:39 -0400 Subject: [PATCH 16/93] . --- dot/state/grandpa.go | 1 - 1 file changed, 1 deletion(-) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 738d14d302..a9cd7f4e1d 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -441,7 +441,6 @@ func (s *GrandpaState) forcedChangeOnChainOf(bestBlockHash common.Hash) (change // scheduledChangeOnChainOf walk only through the scheduled changes roots slice looking for // a scheduled change that belong to the same branch as bestBlockHash parameter func (s *GrandpaState) scheduledChangeOnChainOf(bestBlockHash common.Hash) (change *pendingChange, err error) { - for _, scheduledChange := range s.scheduledChanges { var isDescendant bool isDescendant, err = s.blockState.IsDescendantOf( From c2314390376e22111cdd0df90f66bd3ea4ccebcc Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Tue, 12 Apr 2022 09:20:09 -0400 Subject: [PATCH 17/93] wip: testing standard changes --- dot/digest/digest.go | 16 ++++--- dot/digest/interface.go | 4 +- dot/state/grandpa.go | 94 ++++++++++++++++++++++------------------- dot/types/digest.go | 4 ++ 4 files changed, 66 insertions(+), 52 deletions(-) diff --git a/dot/digest/digest.go b/dot/digest/digest.go index 3ab5b57709..3b591ff68a 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -19,6 +19,10 @@ var ( _ services.Service = &Handler{} ) +var ( + ErrUnknownConsensusID = errors.New("unknown consensus engine ID") +) + // Handler is used to handle consensus messages and relevant authority updates to BABE and GRANDPA type Handler struct { ctx context.Context @@ -117,7 +121,7 @@ func (h *Handler) handleConsensusDigest(d *types.ConsensusDigest, header *types. return err } - return h.grandpaState.AddPendingChange(header, data) + return h.grandpaState.HandleGRANDPADigest(header, data) case types.BabeEngineID: data := types.NewBabeConsensusDigest() err := scale.Unmarshal(d.Data, &data) @@ -126,13 +130,9 @@ func (h *Handler) handleConsensusDigest(d *types.ConsensusDigest, header *types. } return h.handleBabeConsensusDigest(data, header) + default: + return fmt.Errorf("%w: %s", ErrUnknownConsensusID, d.ConsensusEngineID.ToBytes()) } - - return errors.New("unknown consensus engine ID") -} - -func (h *Handler) handleGrandpaConsensusDigest(digest scale.VaryingDataType, header *types.Header) error { - return h.grandpaState.AddPendingChange(header, digest) } func (h *Handler) handleBabeConsensusDigest(digest scale.VaryingDataType, header *types.Header) error { @@ -204,6 +204,8 @@ func (h *Handler) handleBlockFinalisation(ctx context.Context) { h.logger.Errorf("failed to store babe next epoch digest: %s", err) } + err = h.grandpaState.ApplyScheduledChanges(&info.Header) + err = h.handleGrandpaChangesOnFinalization(info.Header.Number) if err != nil { h.logger.Errorf("failed to handle grandpa changes on block finalisation: %s", err) diff --git a/dot/digest/interface.go b/dot/digest/interface.go index 6239ad50f8..7785f0b61c 100644 --- a/dot/digest/interface.go +++ b/dot/digest/interface.go @@ -41,5 +41,7 @@ type GrandpaState interface { SetNextPause(number uint) error SetNextResume(number uint) error GetCurrentSetID() (uint64, error) - AddPendingChange(header *types.Header, digest scale.VaryingDataType) error + + HandleGRANDPADigest(header *types.Header, digest scale.VaryingDataType) error + ApplyScheduledChanges(finalizedheader *types.Header) error } diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index a9cd7f4e1d..c971c8cef4 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -33,16 +33,31 @@ var ( ErrLowerThanBestFinalized = errors.New("current finalized is lower than best finalized header") ) +type pendingChange struct { + delay uint32 + nextAuthorities []types.Authority + announcingHeader *types.Header +} + +func (p pendingChange) String() string { + return fmt.Sprintf("announcing header: %s (%d), delay: %d, next authorities: %d", + p.announcingHeader.Hash(), p.announcingHeader.Number, p.delay, len(p.nextAuthorities)) +} + +func (p *pendingChange) effectiveNumber() uint { + return p.announcingHeader.Number + uint(p.delay) +} + type isDescendantOfFunc func(parent, child common.Hash) (bool, error) -type changeNode struct { +type pendingChangeNode struct { header *types.Header change *pendingChange - - nodes []*changeNode + nodes []*pendingChangeNode } -func (c *changeNode) importScheduledChange(header *types.Header, pendingChange *pendingChange, isDescendantOf isDescendantOfFunc) (imported bool, err error) { +func (c *pendingChangeNode) importScheduledChange(header *types.Header, pendingChange *pendingChange, + isDescendantOf isDescendantOfFunc) (imported bool, err error) { if c.header.Hash() == header.Hash() { return false, errors.New("duplicate block hash while importing change") } @@ -71,26 +86,11 @@ func (c *changeNode) importScheduledChange(header *types.Header, pendingChange * return false, nil } - changeNode := &changeNode{header: header, change: pendingChange, nodes: []*changeNode{}} - c.nodes = append(c.nodes, changeNode) + pendingChangeNode := &pendingChangeNode{header: header, change: pendingChange, nodes: []*pendingChangeNode{}} + c.nodes = append(c.nodes, pendingChangeNode) return true, nil } -type pendingChange struct { - delay uint32 - nextAuthorities []types.Authority - announcingHeader *types.Header -} - -func (p pendingChange) String() string { - return fmt.Sprintf("announcing header: %s (%d), delay: %d, next authorities: %d", - p.announcingHeader.Hash(), p.announcingHeader.Number, p.delay, len(p.nextAuthorities)) -} - -func (p *pendingChange) effectiveNumber() uint { - return p.announcingHeader.Number + uint(p.delay) -} - type orderedPendingChanges []*pendingChange func (o orderedPendingChanges) Len() int { return len(o) } @@ -109,8 +109,8 @@ type GrandpaState struct { forksLock sync.RWMutex - scheduledChanges []*changeNode - forcedChanges orderedPendingChanges + scheduledChangeRoots []*pendingChangeNode + forcedChanges orderedPendingChanges } // NewGrandpaStateFromGenesis returns a new GrandpaState given the grandpa genesis authorities @@ -149,7 +149,8 @@ func NewGrandpaState(db chaindb.Database, bs *BlockState) (*GrandpaState, error) }, nil } -func (s *GrandpaState) AddPendingChange(header *types.Header, digest scale.VaryingDataType) error { +// HandleGRANDPADigest receives a decoded GRANDPA digest and calls the right function to handles the digest +func (s *GrandpaState) HandleGRANDPADigest(header *types.Header, digest scale.VaryingDataType) error { switch val := digest.Value().(type) { case types.GrandpaScheduledChange: return s.addScheduledChange(header, val) @@ -219,6 +220,10 @@ func (s *GrandpaState) addScheduledChange(header *types.Header, sc types.Grandpa } func (s *GrandpaState) importScheduledChange(header *types.Header, pendingChange *pendingChange) error { + defer func() { + logger.Debugf("there are now %d possible standard changes (roots)", len(s.scheduledChangeRoots)) + }() + highestFinalizedHeader, err := s.blockState.GetHighestFinalisedHeader() if err != nil { return fmt.Errorf("cannot get highest finalized header: %w", err) @@ -228,7 +233,7 @@ func (s *GrandpaState) importScheduledChange(header *types.Header, pendingChange return errors.New("cannot import changes from blocks older then our highest finalized block") } - for _, root := range s.scheduledChanges { + for _, root := range s.scheduledChangeRoots { imported, err := root.importScheduledChange(header, pendingChange, s.blockState.IsDescendantOf) if err != nil { @@ -236,12 +241,13 @@ func (s *GrandpaState) importScheduledChange(header *types.Header, pendingChange } if imported { + logger.Debugf("changes on header %s (%d) imported succesfully", header.Hash(), header.Number) return nil } } - changeNode := &changeNode{header: header, change: pendingChange, nodes: []*changeNode{}} - s.scheduledChanges = append(s.scheduledChanges, changeNode) + pendingChangeNode := &pendingChangeNode{header: header, change: pendingChange, nodes: []*pendingChangeNode{}} + s.scheduledChangeRoots = append(s.scheduledChangeRoots, pendingChangeNode) return nil } @@ -259,14 +265,14 @@ func (s *GrandpaState) finalizedScheduledChange(finalizedHeader *types.Header) ( return false, nil, ErrLowerThanBestFinalized } - effectiveLowerThanFinalized := func(change *pendingChange) bool { + effectiveBlockLowerOrEqualFinalized := func(change *pendingChange) bool { return change.effectiveNumber() <= finalizedHeader.Number } finalizedHash := finalizedHeader.Hash() position := -1 - for idx, root := range s.scheduledChanges { - if !effectiveLowerThanFinalized(root.change) { + for idx, root := range s.scheduledChangeRoots { + if !effectiveBlockLowerOrEqualFinalized(root.change) { continue } @@ -305,11 +311,11 @@ func (s *GrandpaState) finalizedScheduledChange(finalizedHeader *types.Header) ( var changeToApply *pendingChange = nil if position != -1 { - changeNodeAtPosition := s.scheduledChanges[position] - changeToApply = changeNodeAtPosition.change + pendingChangeNodeAtPosition := s.scheduledChangeRoots[position] + changeToApply = pendingChangeNodeAtPosition.change - s.scheduledChanges = make([]*changeNode, len(changeNodeAtPosition.nodes)) - copy(s.scheduledChanges, changeNodeAtPosition.nodes) + s.scheduledChangeRoots = make([]*pendingChangeNode, len(pendingChangeNodeAtPosition.nodes)) + copy(s.scheduledChangeRoots, pendingChangeNodeAtPosition.nodes) } changed, err := s.retainScheduledChangeRoots(finalizedHeader) @@ -324,10 +330,10 @@ func (s *GrandpaState) finalizedScheduledChange(finalizedHeader *types.Header) ( // retainScheduledChangeRoots keeps the child nodes from the applied standard change as the new roots of the // s.scheduledChanges tree func (s *GrandpaState) retainScheduledChangeRoots(finalizedHeader *types.Header) (changed bool, err error) { - newScheduledChangeRoots := []*changeNode{} + newScheduledChangeRoots := []*pendingChangeNode{} finalizedHash := finalizedHeader.Hash() - for _, root := range s.scheduledChanges { + for _, root := range s.scheduledChangeRoots { rootBlockHash := root.header.Hash() isAncestor, err := s.blockState.IsDescendantOf(finalizedHash, rootBlockHash) @@ -351,14 +357,14 @@ func (s *GrandpaState) retainScheduledChangeRoots(finalizedHeader *types.Header) } } - s.scheduledChanges = make([]*changeNode, len(newScheduledChangeRoots)) - copy(s.scheduledChanges, newScheduledChangeRoots) + s.scheduledChangeRoots = make([]*pendingChangeNode, len(newScheduledChangeRoots)) + copy(s.scheduledChangeRoots, newScheduledChangeRoots) return changed, nil } -// applyScheduledChange will check the schedules changes in order to find a root +// ApplyScheduledChange will check the schedules changes in order to find a root // that is equals or behind the finalized number and will apply its authority set changes -func (s *GrandpaState) applyScheduledChange(finalizedHeader *types.Header) error { +func (s *GrandpaState) ApplyScheduledChanges(finalizedHeader *types.Header) error { changed, changeToApply, err := s.finalizedScheduledChange(finalizedHeader) if err != nil { return fmt.Errorf("cannot finalize scheduled change: %w", err) @@ -414,9 +420,9 @@ func (s *GrandpaState) applyScheduledChange(finalizedHeader *types.Header) error return nil } -// forcedChangeOnChainOf walk through the forced change slice looking for +// forcedChangeOnChain walk through the forced change slice looking for // a forced change that belong to the same branch as bestBlockHash parameter -func (s *GrandpaState) forcedChangeOnChainOf(bestBlockHash common.Hash) (change *pendingChange, err error) { +func (s *GrandpaState) forcedChangeOnChain(bestBlockHash common.Hash) (change *pendingChange, err error) { for _, forcedChange := range s.forcedChanges { forcedChangeHeader := forcedChange.announcingHeader @@ -441,7 +447,7 @@ func (s *GrandpaState) forcedChangeOnChainOf(bestBlockHash common.Hash) (change // scheduledChangeOnChainOf walk only through the scheduled changes roots slice looking for // a scheduled change that belong to the same branch as bestBlockHash parameter func (s *GrandpaState) scheduledChangeOnChainOf(bestBlockHash common.Hash) (change *pendingChange, err error) { - for _, scheduledChange := range s.scheduledChanges { + for _, scheduledChange := range s.scheduledChangeRoots { var isDescendant bool isDescendant, err = s.blockState.IsDescendantOf( scheduledChange.header.Hash(), bestBlockHash) @@ -463,7 +469,7 @@ func (s *GrandpaState) scheduledChangeOnChainOf(bestBlockHash common.Hash) (chan // NextGrandpaAuthorityChange returns the block number of the next upcoming grandpa authorities change. // It returns 0 if no change is scheduled. func (s *GrandpaState) NextGrandpaAuthorityChange(bestBlockHash common.Hash) (blockNumber uint, err error) { - forcedChange, err := s.forcedChangeOnChainOf(bestBlockHash) + forcedChange, err := s.forcedChangeOnChain(bestBlockHash) if err != nil { return 0, fmt.Errorf("cannot get forced change: %w", err) } diff --git a/dot/types/digest.go b/dot/types/digest.go index e0796dcca1..12d30e3296 100644 --- a/dot/types/digest.go +++ b/dot/types/digest.go @@ -29,6 +29,10 @@ func (h ConsensusEngineID) ToBytes() []byte { return b[:] } +func (h ConsensusEngineID) String() string { + return string(h.ToBytes()) +} + // BabeEngineID is the hard-coded babe ID var BabeEngineID = ConsensusEngineID{'B', 'A', 'B', 'E'} From 931e8a0015203a04ccfa2d449dec64e20d5a82d6 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Tue, 12 Apr 2022 11:28:45 -0400 Subject: [PATCH 18/93] chore: split into two different functions and check for existence before persit --- dot/digest/digest.go | 45 ++----- dot/digest/digest_test.go | 105 ---------------- dot/digest/interface.go | 5 +- dot/digest/mock_epoch_state_test.go | 20 +-- dot/state/epoch.go | 79 +++++++----- dot/state/epoch_test.go | 183 +++++++++++++--------------- 6 files changed, 140 insertions(+), 297 deletions(-) diff --git a/dot/digest/digest.go b/dot/digest/digest.go index 6bca24a510..631be163f8 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -8,7 +8,6 @@ import ( "errors" "fmt" - "github.com/ChainSafe/gossamer/dot/state" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/internal/log" "github.com/ChainSafe/gossamer/lib/services" @@ -240,9 +239,14 @@ func (h *Handler) handleBlockFinalisation(ctx context.Context) { continue } - err := h.persistBABEDigestsForNextEpoch(&info.Header) + err := h.epochState.FinalizeBABENextEpochData(&info.Header) if err != nil { - h.logger.Errorf("failed to store babe next epoch digest: %s", err) + h.logger.Errorf("failed to persist babe next epoch data: %s", err) + } + + err = h.epochState.FinalizeBABENextConfigData(&info.Header) + if err != nil { + h.logger.Errorf("failed to persist babe next epoch config: %s", err) } err = h.handleGrandpaChangesOnFinalization(info.Header.Number) @@ -255,41 +259,6 @@ func (h *Handler) handleBlockFinalisation(ctx context.Context) { } } -// persistBABEDigestsForNextEpoch is called only when a block is finalised -// and defines the correct next epoch data and next config data. -func (h *Handler) persistBABEDigestsForNextEpoch(finalizedHeader *types.Header) error { - currEpoch, err := h.epochState.GetEpochForBlock(finalizedHeader) - if err != nil { - return fmt.Errorf("cannot get epoch for block %d (%s): %w", - finalizedHeader.Number, finalizedHeader.Hash(), err) - } - - nextEpoch := currEpoch + 1 - - appliedEpochData, appliedConfigData, err := h.epochState.AlreadyDefined(nextEpoch) - if err != nil { - return fmt.Errorf("cannot check if next epoch is already defined: %w", err) - } - - if !appliedEpochData { - err = h.epochState.FinalizeBABENextEpochData(nextEpoch) - if err != nil && !errors.Is(err, state.ErrEpochNotInMemory) { - return fmt.Errorf("cannot finalize babe next epoch data for block number %d (%s): %w", - finalizedHeader.Number, finalizedHeader.Hash(), err) - } - } - - if !appliedConfigData { - err = h.epochState.FinalizeBABENextConfigData(nextEpoch) - if err != nil && !errors.Is(err, state.ErrEpochNotInMemory) { - return fmt.Errorf("cannot finalize babe next config data for block number %d (%s): %w", - finalizedHeader.Number, finalizedHeader.Hash(), err) - } - } - - return nil -} - func (h *Handler) handleGrandpaChangesOnImport(num uint) error { resume := h.grandpaResume if resume != nil && num >= resume.atBlock { diff --git a/dot/digest/digest_test.go b/dot/digest/digest_test.go index 6e1800e3c7..dcb0d8633b 100644 --- a/dot/digest/digest_test.go +++ b/dot/digest/digest_test.go @@ -4,7 +4,6 @@ package digest import ( - "errors" "testing" "time" @@ -516,107 +515,3 @@ func TestHandler_HandleNextConfigData(t *testing.T) { require.NoError(t, err) require.Equal(t, act.ToConfigData(), stored) } - -func Test_persistBABEDigestsForNextEpoch(t *testing.T) { - const currentEpoch uint64 = 1 - - type testStruct struct { - expectedErr error - epochState func(ctrl *gomock.Controller) EpochState - } - - tests := map[string]testStruct{ - "epoch_data_already_defined": { - epochState: func(ctrl *gomock.Controller) EpochState { - epochStateMock := NewMockEpochState(ctrl) - - epochStateMock.EXPECT().GetEpochForBlock(&types.Header{}). - Return(currentEpoch, nil) - - epochStateMock.EXPECT().AlreadyDefined(currentEpoch+1). - Return(true, false, nil) - - epochStateMock.EXPECT().FinalizeBABENextConfigData(currentEpoch + 1) - return epochStateMock - }, - }, - "config_data_already_defined": { - epochState: func(ctrl *gomock.Controller) EpochState { - epochStateMock := NewMockEpochState(ctrl) - - epochStateMock.EXPECT().GetEpochForBlock(&types.Header{}). - Return(currentEpoch, nil) - - epochStateMock.EXPECT().AlreadyDefined(currentEpoch+1). - Return(false, true, nil) - - epochStateMock.EXPECT().FinalizeBABENextEpochData(currentEpoch + 1) - return epochStateMock - }, - }, - "both_already_defined": { - epochState: func(ctrl *gomock.Controller) EpochState { - epochStateMock := NewMockEpochState(ctrl) - - epochStateMock.EXPECT().GetEpochForBlock(&types.Header{}). - Return(currentEpoch, nil) - - epochStateMock.EXPECT().AlreadyDefined(currentEpoch+1). - Return(true, true, nil) - - return epochStateMock - }, - }, - "both_not_defined": { - epochState: func(ctrl *gomock.Controller) EpochState { - epochStateMock := NewMockEpochState(ctrl) - - epochStateMock.EXPECT().GetEpochForBlock(&types.Header{}). - Return(currentEpoch, nil) - - epochStateMock.EXPECT().AlreadyDefined(currentEpoch+1). - Return(false, false, nil) - - epochStateMock.EXPECT().FinalizeBABENextEpochData(currentEpoch + 1) - epochStateMock.EXPECT().FinalizeBABENextConfigData(currentEpoch + 1) - - return epochStateMock - }, - }, - "error_while_checking_already_defined": { - epochState: func(ctrl *gomock.Controller) EpochState { - epochStateMock := NewMockEpochState(ctrl) - - epochStateMock.EXPECT().GetEpochForBlock(&types.Header{}). - Return(currentEpoch, nil) - - epochStateMock.EXPECT().AlreadyDefined(currentEpoch+1). - Return(false, false, errors.New("problems while calling function")) - - return epochStateMock - }, - expectedErr: errors.New( - "cannot check if next epoch is already defined: problems while calling function"), - }, - } - - for testName, tt := range tests { - tt := tt - t.Run(testName, func(t *testing.T) { - ctrl := gomock.NewController(t) - epochStateMock := tt.epochState(ctrl) - - digestHandler := &Handler{ - epochState: epochStateMock, - } - - err := digestHandler.persistBABEDigestsForNextEpoch(&types.Header{}) - if tt.expectedErr != nil { - require.Error(t, err) - require.EqualError(t, err, tt.expectedErr.Error()) - } else { - require.NoError(t, err) - } - }) - } -} diff --git a/dot/digest/interface.go b/dot/digest/interface.go index 1ef2b561a8..268ae36c7b 100644 --- a/dot/digest/interface.go +++ b/dot/digest/interface.go @@ -28,9 +28,8 @@ type EpochState interface { StoreBABENextEpochData(epoch uint64, hash common.Hash, nextEpochData types.NextEpochData) StoreBABENextConfigData(epoch uint64, hash common.Hash, nextEpochData types.NextConfigData) - FinalizeBABENextEpochData(epoch uint64) error - FinalizeBABENextConfigData(epoch uint64) error - AlreadyDefined(epoch uint64) (epochData bool, configData bool, err error) + FinalizeBABENextEpochData(finalizedHeader *types.Header) error + FinalizeBABENextConfigData(finalizedHeader *types.Header) error } // GrandpaState is the interface for the state.GrandpaState diff --git a/dot/digest/mock_epoch_state_test.go b/dot/digest/mock_epoch_state_test.go index 9b2ea3c305..943e4cad7d 100644 --- a/dot/digest/mock_epoch_state_test.go +++ b/dot/digest/mock_epoch_state_test.go @@ -35,24 +35,8 @@ func (m *MockEpochState) EXPECT() *MockEpochStateMockRecorder { return m.recorder } -// AlreadyDefined mocks base method. -func (m *MockEpochState) AlreadyDefined(arg0 uint64) (bool, bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AlreadyDefined", arg0) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(bool) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// AlreadyDefined indicates an expected call of AlreadyDefined. -func (mr *MockEpochStateMockRecorder) AlreadyDefined(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AlreadyDefined", reflect.TypeOf((*MockEpochState)(nil).AlreadyDefined), arg0) -} - // FinalizeBABENextConfigData mocks base method. -func (m *MockEpochState) FinalizeBABENextConfigData(arg0 uint64) error { +func (m *MockEpochState) FinalizeBABENextConfigData(arg0 *types.Header) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "FinalizeBABENextConfigData", arg0) ret0, _ := ret[0].(error) @@ -66,7 +50,7 @@ func (mr *MockEpochStateMockRecorder) FinalizeBABENextConfigData(arg0 interface{ } // FinalizeBABENextEpochData mocks base method. -func (m *MockEpochState) FinalizeBABENextEpochData(arg0 uint64) error { +func (m *MockEpochState) FinalizeBABENextEpochData(arg0 *types.Header) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "FinalizeBABENextEpochData", arg0) ret0, _ := ret[0].(error) diff --git a/dot/state/epoch.go b/dot/state/epoch.go index 1e97b78e97..0097a9af5a 100644 --- a/dot/state/epoch.go +++ b/dot/state/epoch.go @@ -512,30 +512,6 @@ func (s *EpochState) SkipVerify(header *types.Header) (bool, error) { return false, nil } -// AlreadyDefined will true for each returned value if it is already defined in the database for the given epoch -func (s *EpochState) AlreadyDefined(epoch uint64) (epochData bool, configData bool, err error) { - keys := [...][]byte{epochDataKey(epoch), configDataKey(epoch)} - applied := [...]bool{false, false} - - for idx, key := range keys { - enc, err := s.db.Get(key) - - if errors.Is(err, chaindb.ErrKeyNotFound) { - continue - } else if err != nil { - return false, false, fmt.Errorf("cannot retrieve %s from database: %w", string(key), err) - } - - if len(enc) == 0 { - continue - } - - applied[idx] = true - } - - return applied[0], applied[1], nil -} - // StoreBABENextEpochData stores the types.NextEpochData under epoch and hash keys func (s *EpochState) StoreBABENextEpochData(epoch uint64, hash common.Hash, nextEpochData types.NextEpochData) { s.nextEpochDataLock.Lock() @@ -564,11 +540,29 @@ func (s *EpochState) StoreBABENextConfigData(epoch uint64, hash common.Hash, nex // getting the set of hashes from the received epoch and for each hash // check if the header is in the database then it's been finalized and // thus we can also set the corresponding EpochData in the database -func (s *EpochState) FinalizeBABENextEpochData(epoch uint64) error { +func (s *EpochState) FinalizeBABENextEpochData(finalizedHeader *types.Header) error { s.nextEpochDataLock.Lock() defer s.nextEpochDataLock.Unlock() - finalizedNextEpochData, err := lookupForNextEpochPersistedHash(s.nextEpochData, s, epoch) + finalizedBlockEpoch, err := s.GetEpochForBlock(finalizedHeader) + if err != nil { + return fmt.Errorf("cannot get epoch for block %d (%s): %w", + finalizedHeader.Number, finalizedHeader.Hash(), err) + } + + nextEpoch := finalizedBlockEpoch + 1 + + epochInDatabase, err := s.getEpochDataInDatabase(nextEpoch) + if err != nil && !errors.Is(err, chaindb.ErrKeyNotFound) { + return fmt.Errorf("cannot check if next epoch data is already defined for epoch %d: %w", nextEpoch, err) + } + + // config already defined we don't need to lookup in the map + if epochInDatabase != nil { + return nil + } + + finalizedNextEpochData, err := lookupForNextEpochPersistedHash(s.nextEpochData, s, nextEpoch) if err != nil { return fmt.Errorf("cannot find next epoch data: %w", err) } @@ -578,14 +572,14 @@ func (s *EpochState) FinalizeBABENextEpochData(epoch uint64) error { return fmt.Errorf("cannot transform epoch data: %w", err) } - err = s.SetEpochData(epoch, ed) + err = s.SetEpochData(nextEpoch, ed) if err != nil { return fmt.Errorf("cannot set epoch data: %w", err) } // remove previous epochs from the memory for e := range s.nextEpochData { - if e <= epoch { + if e <= nextEpoch { delete(s.nextEpochData, e) } } @@ -597,24 +591,45 @@ func (s *EpochState) FinalizeBABENextEpochData(epoch uint64) error { // getting the set of hashes from the received epoch and for each hash // check if the header is in the database then it's been finalized and // thus we can also set the corresponding NextConfigData in the database -func (s *EpochState) FinalizeBABENextConfigData(epoch uint64) error { +func (s *EpochState) FinalizeBABENextConfigData(finalizedHeader *types.Header) error { s.nextConfigDataLock.Lock() defer s.nextConfigDataLock.Unlock() - finalizedNextConfigData, err := lookupForNextEpochPersistedHash(s.nextConfigData, s, epoch) + finalizedBlockEpoch, err := s.GetEpochForBlock(finalizedHeader) if err != nil { + return fmt.Errorf("cannot get epoch for block %d (%s): %w", + finalizedHeader.Number, finalizedHeader.Hash(), err) + } + + nextEpoch := finalizedBlockEpoch + 1 + + configInDatabase, err := s.getConfigDataInDatabase(nextEpoch) + if err != nil && !errors.Is(err, chaindb.ErrKeyNotFound) { + return fmt.Errorf("cannot check if next epoch config is already defined for epoch %d: %w", nextEpoch, err) + } + + // config already defined we don't need to lookup in the map + if configInDatabase != nil { + return nil + } + + // not every epoch will have `ConfigData` + finalizedNextConfigData, err := lookupForNextEpochPersistedHash(s.nextConfigData, s, nextEpoch) + if errors.Is(err, ErrEpochNotInMemory) { + return nil + } else if err != nil { return fmt.Errorf("cannot find next config data: %w", err) } cd := finalizedNextConfigData.ToConfigData() - err = s.SetConfigData(epoch, cd) + err = s.SetConfigData(nextEpoch, cd) if err != nil { return fmt.Errorf("cannot set config data: %w", err) } // remove previous epochs from the memory for e := range s.nextConfigData { - if e <= epoch { + if e <= nextEpoch { delete(s.nextConfigData, e) } } diff --git a/dot/state/epoch_test.go b/dot/state/epoch_test.go index 655a7d8fd0..112e347260 100644 --- a/dot/state/epoch_test.go +++ b/dot/state/epoch_test.go @@ -252,17 +252,40 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { } } + babePrimaryPreDigest := types.BabePrimaryPreDigest{ + SlotNumber: 301, // block on epoch 1 with digest for epoch 2 + VRFOutput: [32]byte{}, + VRFProof: [64]byte{}, + } + + preRuntimeDigest, err := babePrimaryPreDigest.ToPreRuntimeDigest() + require.NoError(t, err) + + digest := scale.NewVaryingDataTypeSlice(scale.MustNewVaryingDataType( + types.PreRuntimeDigest{})) + + require.NoError(t, digest.Add(*preRuntimeDigest)) + + // a random finalized header for testing purposes + finalizedHeader := &types.Header{ + ParentHash: common.Hash{}, + Number: 1, + Digest: digest, + } + + finalizedHeaderHash := finalizedHeader.Hash() + tests := map[string]struct { - finalizeHash common.Hash + finalizedHeader *types.Header inMemoryEpoch []inMemoryBABEData[types.NextEpochData] finalizeEpoch uint64 expectErr error shouldRemainInMemory int }{ - "store_and_finalize_successfully": { + "store_and_finalize_succesfully": { shouldRemainInMemory: 1, finalizeEpoch: 2, - finalizeHash: common.MustHexToHash("0x68a27df5a52ff2251df2cc8368f7dcefb305a13bb3d89b65c8fb070f23877f2c"), + finalizedHeader: finalizedHeader, inMemoryEpoch: []inMemoryBABEData[types.NextEpochData]{ { epoch: 1, @@ -291,7 +314,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { hashes: []common.Hash{ common.MustHexToHash("0x5b940c7fc0a1c5a58e4d80c5091dd003303b8f18e90a989f010c1be6f392bed1"), common.MustHexToHash("0xd380bee22de487a707cbda65dd9d4e2188f736908c42cf390c8919d4f7fc547c"), - common.MustHexToHash("0x68a27df5a52ff2251df2cc8368f7dcefb305a13bb3d89b65c8fb070f23877f2c"), + finalizedHeaderHash, }, Data: []types.NextEpochData{ { @@ -324,12 +347,13 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { }, "cannot_finalize_hash_not_stored": { shouldRemainInMemory: 1, - finalizeEpoch: 1, - finalizeHash: common.Hash{}, // finalize when the hash does not exists - expectErr: errHashNotPersisted, + finalizeEpoch: 2, + // this header hash is not in the database + finalizedHeader: finalizedHeader, + expectErr: errHashNotPersisted, inMemoryEpoch: []inMemoryBABEData[types.NextEpochData]{ { - epoch: 1, + epoch: 2, hashes: []common.Hash{ common.MustHexToHash("0x9da3ce2785da743bfbc13449db7dcb7a69c07ca914276d839abe7bedc6ac8fed"), common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"), @@ -353,34 +377,11 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { }, }, "cannot_finalize_in_memory_epoch_not_found": { - shouldRemainInMemory: 1, + shouldRemainInMemory: 0, finalizeEpoch: 3, // try to finalize a epoch that does not exists - finalizeHash: common.Hash{}, + finalizedHeader: finalizedHeader, expectErr: ErrEpochNotInMemory, - inMemoryEpoch: []inMemoryBABEData[types.NextEpochData]{ - { - epoch: 1, - hashes: []common.Hash{ - common.MustHexToHash("0x9da3ce2785da743bfbc13449db7dcb7a69c07ca914276d839abe7bedc6ac8fed"), - common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"), - common.MustHexToHash("0xc0096358534ec8d21d01d34b836eed476a1c343f8724fa2153dc0725ad797a90"), - }, - Data: []types.NextEpochData{ - { - Authorities: authorities[:3], - Randomness: [32]byte{1}, - }, - { - Authorities: authorities[3:6], - Randomness: [32]byte{2}, - }, - { - Authorities: authorities[6:], - Randomness: [32]byte{3}, - }, - }, - }, - }, + inMemoryEpoch: []inMemoryBABEData[types.NextEpochData]{}, }, } @@ -396,29 +397,17 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { require.Len(t, epochState.nextEpochData, len(tt.inMemoryEpoch)) - expectedNextEpochData := epochState.nextEpochData[tt.finalizeEpoch][tt.finalizeHash] - - err := epochState.blockState.db.Put(headerKey(tt.finalizeHash), []byte{}) - require.NoError(t, err) + expectedNextEpochData := epochState.nextEpochData[tt.finalizeEpoch][tt.finalizedHeader.Hash()] - // before finalize next epoch data for this epoch should not be defined - definedEpochData, definedConfigData, err := epochState.AlreadyDefined(tt.finalizeEpoch) + err := epochState.blockState.db.Put(headerKey(tt.finalizedHeader.Hash()), []byte{}) require.NoError(t, err) - require.False(t, definedEpochData) - require.False(t, definedConfigData) - err = epochState.FinalizeBABENextEpochData(tt.finalizeEpoch) + err = epochState.FinalizeBABENextEpochData(tt.finalizedHeader) if tt.expectErr != nil { require.ErrorIs(t, err, tt.expectErr) } else { require.NoError(t, err) - // after finalize next epoch data for this epoch should not defined - definedEpochData, definedConfigData, err := epochState.AlreadyDefined(tt.finalizeEpoch) - require.NoError(t, err) - require.True(t, definedEpochData) - require.False(t, definedConfigData) - expected, err := expectedNextEpochData.ToEpochData() require.NoError(t, err) @@ -435,8 +424,31 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { } func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { + babePrimaryPreDigest := types.BabePrimaryPreDigest{ + SlotNumber: 301, // block on epoch 1 with changes to epoch 2 + VRFOutput: [32]byte{}, + VRFProof: [64]byte{}, + } + + preRuntimeDigest, err := babePrimaryPreDigest.ToPreRuntimeDigest() + require.NoError(t, err) + + digest := scale.NewVaryingDataTypeSlice(scale.MustNewVaryingDataType( + types.PreRuntimeDigest{})) + + require.NoError(t, digest.Add(*preRuntimeDigest)) + + // a random finalized header for testing purposes + finalizedHeader := &types.Header{ + ParentHash: common.Hash{}, + Number: 1, + Digest: digest, + } + + finalizedHeaderHash := finalizedHeader.Hash() + tests := map[string]struct { - finalizeHash common.Hash + finalizedHeader *types.Header inMemoryEpoch []inMemoryBABEData[types.NextConfigData] finalizeEpoch uint64 expectErr error @@ -445,7 +457,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { "store_and_finalize_successfully": { shouldRemainInMemory: 1, finalizeEpoch: 2, - finalizeHash: common.MustHexToHash("0x68a27df5a52ff2251df2cc8368f7dcefb305a13bb3d89b65c8fb070f23877f2c"), + finalizedHeader: finalizedHeader, inMemoryEpoch: []inMemoryBABEData[types.NextConfigData]{ { epoch: 1, @@ -477,7 +489,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { hashes: []common.Hash{ common.MustHexToHash("0x5b940c7fc0a1c5a58e4d80c5091dd003303b8f18e90a989f010c1be6f392bed1"), common.MustHexToHash("0xd380bee22de487a707cbda65dd9d4e2188f736908c42cf390c8919d4f7fc547c"), - common.MustHexToHash("0x68a27df5a52ff2251df2cc8368f7dcefb305a13bb3d89b65c8fb070f23877f2c"), + finalizedHeaderHash, }, Data: []types.NextConfigData{ { @@ -514,12 +526,12 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { }, "cannot_finalize_hash_doesnt_exists": { shouldRemainInMemory: 1, - finalizeEpoch: 1, - finalizeHash: common.Hash{}, // finalize when the hash does not exists + finalizeEpoch: 2, + finalizedHeader: finalizedHeader, // finalize when the hash does not exists expectErr: errHashNotPersisted, inMemoryEpoch: []inMemoryBABEData[types.NextConfigData]{ { - epoch: 1, + epoch: 2, hashes: []common.Hash{ common.MustHexToHash("0x9da3ce2785da743bfbc13449db7dcb7a69c07ca914276d839abe7bedc6ac8fed"), common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"), @@ -545,38 +557,11 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { }, }, }, - "cannot_finalize_in_memory_epoch_not_found": { - shouldRemainInMemory: 1, - finalizeEpoch: 3, // try to finalize a epoch that does not exists - finalizeHash: common.Hash{}, - expectErr: ErrEpochNotInMemory, - inMemoryEpoch: []inMemoryBABEData[types.NextConfigData]{ - { - epoch: 1, - hashes: []common.Hash{ - common.MustHexToHash("0x9da3ce2785da743bfbc13449db7dcb7a69c07ca914276d839abe7bedc6ac8fed"), - common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"), - common.MustHexToHash("0xc0096358534ec8d21d01d34b836eed476a1c343f8724fa2153dc0725ad797a90"), - }, - Data: []types.NextConfigData{ - { - C1: 1, - C2: 2, - SecondarySlots: 0, - }, - { - C1: 2, - C2: 3, - SecondarySlots: 1, - }, - { - C1: 3, - C2: 4, - SecondarySlots: 0, - }, - }, - }, - }, + "in_memory_config_not_found_shouldnt_return_error": { + shouldRemainInMemory: 0, + finalizeEpoch: 1, // try to finalize a epoch that does not exists + finalizedHeader: finalizedHeader, + inMemoryEpoch: []inMemoryBABEData[types.NextConfigData]{}, }, } @@ -592,29 +577,25 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { require.Len(t, epochState.nextConfigData, len(tt.inMemoryEpoch)) - expectedConfigData := epochState.nextConfigData[tt.finalizeEpoch][tt.finalizeHash] + // if there is no data in memory we try to finalize the net config data + // it should return nil since next epoch config data will not be in every epoch's first block + if len(tt.inMemoryEpoch) == 0 { + err = epochState.FinalizeBABENextConfigData(tt.finalizedHeader) + require.NoError(t, err) + return + } - err := epochState.blockState.db.Put(headerKey(tt.finalizeHash), []byte{}) - require.NoError(t, err) + expectedConfigData := epochState.nextConfigData[tt.finalizeEpoch][tt.finalizedHeader.Hash()] - // before finalize next config data for this epoch should not be defined - definedEpochData, definedConfigData, err := epochState.AlreadyDefined(tt.finalizeEpoch) + err := epochState.blockState.db.Put(headerKey(tt.finalizedHeader.Hash()), []byte{}) require.NoError(t, err) - require.False(t, definedEpochData) - require.False(t, definedConfigData) - err = epochState.FinalizeBABENextConfigData(tt.finalizeEpoch) + err = epochState.FinalizeBABENextConfigData(tt.finalizedHeader) if tt.expectErr != nil { require.ErrorIs(t, err, tt.expectErr) } else { require.NoError(t, err) - // after finalize next config data for this epoch should be defined - definedEpochData, definedConfigData, err = epochState.AlreadyDefined(tt.finalizeEpoch) - require.NoError(t, err) - require.False(t, definedEpochData) - require.True(t, definedConfigData) - gotConfigData, err := epochState.GetConfigData(tt.finalizeEpoch, nil) require.NoError(t, err) require.Equal(t, expectedConfigData.ToConfigData(), gotConfigData) From 73e3c330bb51c31296665bcce69b5fc3ade100a1 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Tue, 12 Apr 2022 11:35:39 -0400 Subject: [PATCH 19/93] chore: fix the ci lint warns --- dot/state/epoch_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dot/state/epoch_test.go b/dot/state/epoch_test.go index 112e347260..6953873f85 100644 --- a/dot/state/epoch_test.go +++ b/dot/state/epoch_test.go @@ -282,7 +282,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { expectErr error shouldRemainInMemory int }{ - "store_and_finalize_succesfully": { + "store_and_finalize_successfully": { shouldRemainInMemory: 1, finalizeEpoch: 2, finalizedHeader: finalizedHeader, From 3906c65a3c22a1db9fed7e59da358f276fc5fe89 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Tue, 12 Apr 2022 16:54:16 -0400 Subject: [PATCH 20/93] wip --- dot/digest/digest.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/dot/digest/digest.go b/dot/digest/digest.go index 3b591ff68a..91dd6d1084 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -180,7 +180,6 @@ func (h *Handler) handleBlockImport(ctx context.Context) { } h.HandleDigests(&block.Header) - err := h.handleGrandpaChangesOnImport(block.Header.Number) if err != nil { h.logger.Errorf("failed to handle grandpa changes on block import: %s", err) @@ -205,11 +204,10 @@ func (h *Handler) handleBlockFinalisation(ctx context.Context) { } err = h.grandpaState.ApplyScheduledChanges(&info.Header) - - err = h.handleGrandpaChangesOnFinalization(info.Header.Number) if err != nil { - h.logger.Errorf("failed to handle grandpa changes on block finalisation: %s", err) + h.logger.Errorf("failed to apply standard scheduled changes on block finalization: %w", err) } + case <-ctx.Done(): return } From 38ddcaa0c36734a379365c1d47b12fe071397ec3 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Tue, 12 Apr 2022 16:59:44 -0400 Subject: [PATCH 21/93] chore: use a more descriptive name --- dot/state/epoch.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dot/state/epoch.go b/dot/state/epoch.go index 0097a9af5a..1d86a0ad53 100644 --- a/dot/state/epoch.go +++ b/dot/state/epoch.go @@ -562,7 +562,7 @@ func (s *EpochState) FinalizeBABENextEpochData(finalizedHeader *types.Header) er return nil } - finalizedNextEpochData, err := lookupForNextEpochPersistedHash(s.nextEpochData, s, nextEpoch) + finalizedNextEpochData, err := findFinalizedHeaderForEpoch(s.nextEpochData, s, nextEpoch) if err != nil { return fmt.Errorf("cannot find next epoch data: %w", err) } @@ -614,7 +614,7 @@ func (s *EpochState) FinalizeBABENextConfigData(finalizedHeader *types.Header) e } // not every epoch will have `ConfigData` - finalizedNextConfigData, err := lookupForNextEpochPersistedHash(s.nextConfigData, s, nextEpoch) + finalizedNextConfigData, err := findFinalizedHeaderForEpoch(s.nextConfigData, s, nextEpoch) if errors.Is(err, ErrEpochNotInMemory) { return nil } else if err != nil { @@ -637,10 +637,10 @@ func (s *EpochState) FinalizeBABENextConfigData(finalizedHeader *types.Header) e return nil } -// lookupForNextEpochPersistedHash given a specific epoch (the key) will go through the hashes looking +// findFinalizedHeaderForEpoch given a specific epoch (the key) will go through the hashes looking // for a database persisted hash (belonging to the finalized chain) // which contains the right configuration to be persisted and safely used -func lookupForNextEpochPersistedHash[T types.NextConfigData | types.NextEpochData]( +func findFinalizedHeaderForEpoch[T types.NextConfigData | types.NextEpochData]( nextEpochMap map[uint64]map[common.Hash]T, es *EpochState, epoch uint64) (next *T, err error) { hashes, has := nextEpochMap[epoch] if !has { From 27b016c0dc3496cbaed44f4d2e81b40416d0561a Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Wed, 13 Apr 2022 09:05:39 -0400 Subject: [PATCH 22/93] wip --- dot/digest/digest.go | 72 ------------------------------------------ dot/state/grandpa.go | 10 +++--- lib/grandpa/grandpa.go | 14 ++++---- 3 files changed, 12 insertions(+), 84 deletions(-) diff --git a/dot/digest/digest.go b/dot/digest/digest.go index 91dd6d1084..14fbe7fb3c 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -180,10 +180,6 @@ func (h *Handler) handleBlockImport(ctx context.Context) { } h.HandleDigests(&block.Header) - err := h.handleGrandpaChangesOnImport(block.Header.Number) - if err != nil { - h.logger.Errorf("failed to handle grandpa changes on block import: %s", err) - } case <-ctx.Done(): return } @@ -248,71 +244,3 @@ func (h *Handler) persistBABEDigestsForNextEpoch(finalizedHeader *types.Header) return nil } - -func (h *Handler) handleGrandpaChangesOnImport(num uint) error { - resume := h.grandpaResume - if resume != nil && num >= resume.atBlock { - h.grandpaResume = nil - } - - fc := h.grandpaForcedChange - if fc != nil && num >= fc.atBlock { - curr, err := h.grandpaState.IncrementSetID() - if err != nil { - return err - } - - h.grandpaForcedChange = nil - h.logger.Debugf("incremented grandpa set id %d", curr) - } - - return nil -} - -func (h *Handler) handleGrandpaChangesOnFinalization(num uint) error { - pause := h.grandpaPause - if pause != nil && num >= pause.atBlock { - h.grandpaPause = nil - } - - sc := h.grandpaScheduledChange - if sc != nil && num >= sc.atBlock { - curr, err := h.grandpaState.IncrementSetID() - if err != nil { - return err - } - - h.grandpaScheduledChange = nil - h.logger.Debugf("incremented grandpa set id %d", curr) - } - - // if blocks get finalised before forced change takes place, disregard it - h.grandpaForcedChange = nil - return nil -} - -func (h *Handler) handlePause(p types.GrandpaPause) error { - curr, err := h.blockState.BestBlockHeader() - if err != nil { - return err - } - - h.grandpaPause = &pause{ - atBlock: curr.Number + uint(p.Delay), - } - - return h.grandpaState.SetNextPause(h.grandpaPause.atBlock) -} - -func (h *Handler) handleResume(r types.GrandpaResume) error { - curr, err := h.blockState.BestBlockHeader() - if err != nil { - return err - } - - h.grandpaResume = &resume{ - atBlock: curr.Number + uint(r.Delay), - } - - return h.grandpaState.SetNextResume(h.grandpaResume.atBlock) -} diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index c971c8cef4..089dc26462 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -27,10 +27,10 @@ var ( ) var ( - ErrAlreadyHasForcedChanges = errors.New("already has a forced change") - ErrUnfinalizedAncestor = errors.New("ancestor with changes not applied") - ErrGetNextAuthorityChangeBlockNumber = errors.New("cannot get the next authority change block number") - ErrLowerThanBestFinalized = errors.New("current finalized is lower than best finalized header") + ErrAlreadyHasForcedChanges = errors.New("already has a forced change") + ErrUnfinalizedAncestor = errors.New("ancestor with changes not applied") + ErrNoChanges = errors.New("cannot get the next authority change block number") + ErrLowerThanBestFinalized = errors.New("current finalized is lower than best finalized header") ) type pendingChange struct { @@ -480,7 +480,7 @@ func (s *GrandpaState) NextGrandpaAuthorityChange(bestBlockHash common.Hash) (bl } if forcedChange == nil && scheduledChange == nil { - return 0, ErrGetNextAuthorityChangeBlockNumber + return 0, ErrNoChanges } if forcedChange.announcingHeader.Number < scheduledChange.announcingHeader.Number { diff --git a/lib/grandpa/grandpa.go b/lib/grandpa/grandpa.go index f851c58d59..b2b177faf8 100644 --- a/lib/grandpa/grandpa.go +++ b/lib/grandpa/grandpa.go @@ -238,7 +238,7 @@ func (s *Service) authorities() []*types.Authority { func (s *Service) updateAuthorities() error { currSetID, err := s.grandpaState.GetCurrentSetID() if err != nil { - return err + return fmt.Errorf("cannot get current set id: %w", err) } // set ID hasn't changed, do nothing @@ -248,7 +248,7 @@ func (s *Service) updateAuthorities() error { nextAuthorities, err := s.grandpaState.GetAuthorities(currSetID) if err != nil { - return err + return fmt.Errorf("cannot get authorities for set id %d: %w", currSetID, err) } s.state.voters = nextAuthorities @@ -294,12 +294,12 @@ func (s *Service) initiateRound() error { // if there is an authority change, execute it err := s.updateAuthorities() if err != nil { - return err + return fmt.Errorf("cannot update authorities while initiating the round: %w", err) } round, setID, err := s.blockState.GetHighestRoundAndSetID() if err != nil { - return err + return fmt.Errorf("cannot get highest round and set id: %w", err) } if round > s.state.round && setID == s.state.setID { @@ -512,7 +512,7 @@ func (s *Service) playGrandpaRound() error { go s.sendVoteMessage(prevote, vm, roundComplete) logger.Debug("receiving pre-commit messages...") - // through goroutine s.receiveMessages(ctx) + // through goroutine s.receiveVoteMessages(ctx) time.Sleep(s.interval) if s.paused.Load().(bool) { @@ -701,7 +701,7 @@ func (s *Service) determinePreVote() (*Vote, error) { } nextChange, err := s.grandpaState.NextGrandpaAuthorityChange(beastBlockHeader.Hash()) - if errors.Is(err, state.ErrGetNextAuthorityChangeBlockNumber) { + if errors.Is(err, state.ErrNoChanges) { return vote, nil } else if err != nil { return nil, fmt.Errorf("cannot get next grandpa authority change: %w", err) @@ -732,7 +732,7 @@ func (s *Service) determinePreCommit() (*Vote, error) { bestBlockHash := s.blockState.BestBlockHash() nextChange, err := s.grandpaState.NextGrandpaAuthorityChange(bestBlockHash) - if errors.Is(err, state.ErrGetNextAuthorityChangeBlockNumber) { + if errors.Is(err, state.ErrNoChanges) { return &pvb, nil } else if err != nil { return nil, fmt.Errorf("cannot get next grandpa authority change: %w", err) From dfd06d2407b7d2c16bc02ab76f3963648757a65c Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Wed, 13 Apr 2022 09:11:27 -0400 Subject: [PATCH 23/93] chore: unexport `Data` field on epoch_test.go --- dot/state/epoch_test.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/dot/state/epoch_test.go b/dot/state/epoch_test.go index 6953873f85..d45eea16a1 100644 --- a/dot/state/epoch_test.go +++ b/dot/state/epoch_test.go @@ -229,7 +229,7 @@ func TestEpochState_GetEpochFromTime(t *testing.T) { type inMemoryBABEData[T any] struct { epoch uint64 hashes []common.Hash - Data []T + data []T } func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { @@ -294,7 +294,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"), common.MustHexToHash("0xc0096358534ec8d21d01d34b836eed476a1c343f8724fa2153dc0725ad797a90"), }, - Data: []types.NextEpochData{ + data: []types.NextEpochData{ { Authorities: authorities[:3], Randomness: [32]byte{1}, @@ -316,7 +316,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { common.MustHexToHash("0xd380bee22de487a707cbda65dd9d4e2188f736908c42cf390c8919d4f7fc547c"), finalizedHeaderHash, }, - Data: []types.NextEpochData{ + data: []types.NextEpochData{ { Authorities: authorities[6:], Randomness: [32]byte{1}, @@ -336,7 +336,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { hashes: []common.Hash{ common.MustHexToHash("0xab5c9230a7dde8bb90a6728ba4a0165423294dac14336b1443f865b796ff682c"), }, - Data: []types.NextEpochData{ + data: []types.NextEpochData{ { Authorities: authorities[6:], Randomness: [32]byte{1}, @@ -359,7 +359,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"), common.MustHexToHash("0xc0096358534ec8d21d01d34b836eed476a1c343f8724fa2153dc0725ad797a90"), }, - Data: []types.NextEpochData{ + data: []types.NextEpochData{ { Authorities: authorities[:3], Randomness: [32]byte{1}, @@ -391,7 +391,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { for _, e := range tt.inMemoryEpoch { for i, hash := range e.hashes { - epochState.StoreBABENextEpochData(e.epoch, hash, e.Data[i]) + epochState.StoreBABENextEpochData(e.epoch, hash, e.data[i]) } } @@ -466,7 +466,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"), common.MustHexToHash("0xc0096358534ec8d21d01d34b836eed476a1c343f8724fa2153dc0725ad797a90"), }, - Data: []types.NextConfigData{ + data: []types.NextConfigData{ { C1: 1, C2: 2, @@ -491,7 +491,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { common.MustHexToHash("0xd380bee22de487a707cbda65dd9d4e2188f736908c42cf390c8919d4f7fc547c"), finalizedHeaderHash, }, - Data: []types.NextConfigData{ + data: []types.NextConfigData{ { C1: 1, C2: 2, @@ -514,7 +514,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { hashes: []common.Hash{ common.MustHexToHash("0xab5c9230a7dde8bb90a6728ba4a0165423294dac14336b1443f865b796ff682c"), }, - Data: []types.NextConfigData{ + data: []types.NextConfigData{ { C1: 1, C2: 2, @@ -537,7 +537,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"), common.MustHexToHash("0xc0096358534ec8d21d01d34b836eed476a1c343f8724fa2153dc0725ad797a90"), }, - Data: []types.NextConfigData{ + data: []types.NextConfigData{ { C1: 1, C2: 2, @@ -571,7 +571,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { for _, e := range tt.inMemoryEpoch { for i, hash := range e.hashes { - epochState.StoreBABENextConfigData(e.epoch, hash, e.Data[i]) + epochState.StoreBABENextConfigData(e.epoch, hash, e.data[i]) } } From 89a119fdc0541455c1c0b93ef0041865e367fc1f Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Wed, 13 Apr 2022 09:15:55 -0400 Subject: [PATCH 24/93] chore: addressing last nits --- dot/state/epoch_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/dot/state/epoch_test.go b/dot/state/epoch_test.go index d45eea16a1..aed2e4451b 100644 --- a/dot/state/epoch_test.go +++ b/dot/state/epoch_test.go @@ -438,7 +438,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { require.NoError(t, digest.Add(*preRuntimeDigest)) - // a random finalized header for testing purposes + // finalized header for testing purposes finalizedHeader := &types.Header{ ParentHash: common.Hash{}, Number: 1, @@ -450,13 +450,13 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { tests := map[string]struct { finalizedHeader *types.Header inMemoryEpoch []inMemoryBABEData[types.NextConfigData] - finalizeEpoch uint64 + finalizedEpoch uint64 expectErr error shouldRemainInMemory int }{ "store_and_finalize_successfully": { shouldRemainInMemory: 1, - finalizeEpoch: 2, + finalizedEpoch: 2, finalizedHeader: finalizedHeader, inMemoryEpoch: []inMemoryBABEData[types.NextConfigData]{ { @@ -526,8 +526,8 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { }, "cannot_finalize_hash_doesnt_exists": { shouldRemainInMemory: 1, - finalizeEpoch: 2, - finalizedHeader: finalizedHeader, // finalize when the hash does not exists + finalizedEpoch: 2, + finalizedHeader: finalizedHeader, // finalize when the hash does not exist expectErr: errHashNotPersisted, inMemoryEpoch: []inMemoryBABEData[types.NextConfigData]{ { @@ -559,7 +559,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { }, "in_memory_config_not_found_shouldnt_return_error": { shouldRemainInMemory: 0, - finalizeEpoch: 1, // try to finalize a epoch that does not exists + finalizedEpoch: 1, // try to finalize an epoch that does not exist finalizedHeader: finalizedHeader, inMemoryEpoch: []inMemoryBABEData[types.NextConfigData]{}, }, @@ -585,7 +585,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { return } - expectedConfigData := epochState.nextConfigData[tt.finalizeEpoch][tt.finalizedHeader.Hash()] + expectedConfigData := epochState.nextConfigData[tt.finalizedEpoch][tt.finalizedHeader.Hash()] err := epochState.blockState.db.Put(headerKey(tt.finalizedHeader.Hash()), []byte{}) require.NoError(t, err) @@ -596,7 +596,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { } else { require.NoError(t, err) - gotConfigData, err := epochState.GetConfigData(tt.finalizeEpoch, nil) + gotConfigData, err := epochState.GetConfigData(tt.finalizedEpoch, nil) require.NoError(t, err) require.Equal(t, expectedConfigData.ToConfigData(), gotConfigData) } From f4cc36a0b0d433882698577ec2bcb61c9887ff14 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Wed, 13 Apr 2022 09:33:08 -0400 Subject: [PATCH 25/93] chore: rename to a better name --- dot/state/epoch.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/dot/state/epoch.go b/dot/state/epoch.go index 1d86a0ad53..b0f0bbada9 100644 --- a/dot/state/epoch.go +++ b/dot/state/epoch.go @@ -244,7 +244,7 @@ func (s *EpochState) SetEpochData(epoch uint64, info *types.EpochData) error { // otherwise will try to get the data from the in-memory map using the header // if the header params is nil then it will search only in database func (s *EpochState) GetEpochData(epoch uint64, header *types.Header) (*types.EpochData, error) { - epochData, err := s.getEpochDataInDatabase(epoch) + epochData, err := s.getEpochDataFromDatabase(epoch) if err == nil && epochData != nil { return epochData, nil } @@ -256,7 +256,7 @@ func (s *EpochState) GetEpochData(epoch uint64, header *types.Header) (*types.Ep return epochData, nil } - epochData, err = s.getEpochDataInMemory(epoch, header) + epochData, err = s.getEpochDataFromMemory(epoch, header) if err != nil { return nil, fmt.Errorf("failed to get epoch data from memory: %w", err) } @@ -264,8 +264,8 @@ func (s *EpochState) GetEpochData(epoch uint64, header *types.Header) (*types.Ep return epochData, nil } -// getEpochDataInDatabase returns the epoch data for a given epoch persisted in database -func (s *EpochState) getEpochDataInDatabase(epoch uint64) (*types.EpochData, error) { +// getEpochDataFromDatabase returns the epoch data for a given epoch persisted in database +func (s *EpochState) getEpochDataFromDatabase(epoch uint64) (*types.EpochData, error) { enc, err := s.db.Get(epochDataKey(epoch)) if err != nil { return nil, err @@ -280,8 +280,8 @@ func (s *EpochState) getEpochDataInDatabase(epoch uint64) (*types.EpochData, err return raw.ToEpochData() } -// getEpochDataInMemory retrieves the right epoch data that belongs to the header parameter -func (s *EpochState) getEpochDataInMemory(epoch uint64, header *types.Header) (*types.EpochData, error) { +// getEpochDataFromMemory retrieves the right epoch data that belongs to the header parameter +func (s *EpochState) getEpochDataFromMemory(epoch uint64, header *types.Header) (*types.EpochData, error) { s.nextEpochDataLock.RLock() defer s.nextEpochDataLock.RUnlock() @@ -359,7 +359,7 @@ func (s *EpochState) setLatestConfigData(epoch uint64) error { // otherwise tries to get the data from the in-memory map using the header. // If the header params is nil then it will search only in the database func (s *EpochState) GetConfigData(epoch uint64, header *types.Header) (*types.ConfigData, error) { - configData, err := s.getConfigDataInDatabase(epoch) + configData, err := s.getConfigDataFromDatabase(epoch) if err == nil && configData != nil { return configData, nil } @@ -371,7 +371,7 @@ func (s *EpochState) GetConfigData(epoch uint64, header *types.Header) (*types.C return configData, nil } - configData, err = s.getConfigDataInMemory(epoch, header) + configData, err = s.getConfigDataFromMemory(epoch, header) if err != nil { return nil, fmt.Errorf("failed to get config data from memory: %w", err) } @@ -379,8 +379,8 @@ func (s *EpochState) GetConfigData(epoch uint64, header *types.Header) (*types.C return configData, nil } -// getConfigDataInDatabase returns the BABE config data for a given epoch persisted in database -func (s *EpochState) getConfigDataInDatabase(epoch uint64) (*types.ConfigData, error) { +// getConfigDataFromDatabase returns the BABE config data for a given epoch persisted in database +func (s *EpochState) getConfigDataFromDatabase(epoch uint64) (*types.ConfigData, error) { enc, err := s.db.Get(configDataKey(epoch)) if err != nil { return nil, err @@ -395,8 +395,8 @@ func (s *EpochState) getConfigDataInDatabase(epoch uint64) (*types.ConfigData, e return info, nil } -// getConfigDataInMemory retrieves the BABE config data for a given epoch that belongs to the header parameter -func (s *EpochState) getConfigDataInMemory(epoch uint64, header *types.Header) (*types.ConfigData, error) { +// getConfigDataFromMemory retrieves the BABE config data for a given epoch that belongs to the header parameter +func (s *EpochState) getConfigDataFromMemory(epoch uint64, header *types.Header) (*types.ConfigData, error) { s.nextConfigDataLock.RLock() defer s.nextConfigDataLock.RUnlock() @@ -552,7 +552,7 @@ func (s *EpochState) FinalizeBABENextEpochData(finalizedHeader *types.Header) er nextEpoch := finalizedBlockEpoch + 1 - epochInDatabase, err := s.getEpochDataInDatabase(nextEpoch) + epochInDatabase, err := s.getEpochDataFromDatabase(nextEpoch) if err != nil && !errors.Is(err, chaindb.ErrKeyNotFound) { return fmt.Errorf("cannot check if next epoch data is already defined for epoch %d: %w", nextEpoch, err) } @@ -603,7 +603,7 @@ func (s *EpochState) FinalizeBABENextConfigData(finalizedHeader *types.Header) e nextEpoch := finalizedBlockEpoch + 1 - configInDatabase, err := s.getConfigDataInDatabase(nextEpoch) + configInDatabase, err := s.getConfigDataFromDatabase(nextEpoch) if err != nil && !errors.Is(err, chaindb.ErrKeyNotFound) { return fmt.Errorf("cannot check if next epoch config is already defined for epoch %d: %w", nextEpoch, err) } From 71ee157e88270b8adfc0fccda14dc884bf4fae82 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Wed, 13 Apr 2022 09:34:26 -0400 Subject: [PATCH 26/93] chore: fix comments --- dot/state/epoch.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dot/state/epoch.go b/dot/state/epoch.go index b0f0bbada9..ca64fdfce4 100644 --- a/dot/state/epoch.go +++ b/dot/state/epoch.go @@ -557,7 +557,7 @@ func (s *EpochState) FinalizeBABENextEpochData(finalizedHeader *types.Header) er return fmt.Errorf("cannot check if next epoch data is already defined for epoch %d: %w", nextEpoch, err) } - // config already defined we don't need to lookup in the map + // epoch data already defined we don't need to lookup in the map if epochInDatabase != nil { return nil } @@ -608,7 +608,7 @@ func (s *EpochState) FinalizeBABENextConfigData(finalizedHeader *types.Header) e return fmt.Errorf("cannot check if next epoch config is already defined for epoch %d: %w", nextEpoch, err) } - // config already defined we don't need to lookup in the map + // config data already defined we don't need to lookup in the map if configInDatabase != nil { return nil } From cdd29eaf87aad35d380635e069dc18a6ae97b2e3 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Wed, 13 Apr 2022 12:37:55 -0400 Subject: [PATCH 27/93] chore: addressing comments --- dot/state/epoch.go | 7 +++++++ dot/state/epoch_test.go | 26 +++++++++++++------------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/dot/state/epoch.go b/dot/state/epoch.go index ca64fdfce4..731349cc8f 100644 --- a/dot/state/epoch.go +++ b/dot/state/epoch.go @@ -553,6 +553,9 @@ func (s *EpochState) FinalizeBABENextEpochData(finalizedHeader *types.Header) er nextEpoch := finalizedBlockEpoch + 1 epochInDatabase, err := s.getEpochDataFromDatabase(nextEpoch) + + // if an error occurs and the error is chaindb.ErrKeyNotFound we ignore + // since this error is what we will handle in the next lines if err != nil && !errors.Is(err, chaindb.ErrKeyNotFound) { return fmt.Errorf("cannot check if next epoch data is already defined for epoch %d: %w", nextEpoch, err) } @@ -604,6 +607,9 @@ func (s *EpochState) FinalizeBABENextConfigData(finalizedHeader *types.Header) e nextEpoch := finalizedBlockEpoch + 1 configInDatabase, err := s.getConfigDataFromDatabase(nextEpoch) + + // if an error occurs and the error is chaindb.ErrKeyNotFound we ignore + // since this error is what we will handle in the next lines if err != nil && !errors.Is(err, chaindb.ErrKeyNotFound) { return fmt.Errorf("cannot check if next epoch config is already defined for epoch %d: %w", nextEpoch, err) } @@ -616,6 +622,7 @@ func (s *EpochState) FinalizeBABENextConfigData(finalizedHeader *types.Header) e // not every epoch will have `ConfigData` finalizedNextConfigData, err := findFinalizedHeaderForEpoch(s.nextConfigData, s, nextEpoch) if errors.Is(err, ErrEpochNotInMemory) { + logger.Debugf("config data for epoch %d not found in memory", nextEpoch) return nil } else if err != nil { return fmt.Errorf("cannot find next config data: %w", err) diff --git a/dot/state/epoch_test.go b/dot/state/epoch_test.go index aed2e4451b..dd9d413866 100644 --- a/dot/state/epoch_test.go +++ b/dot/state/epoch_test.go @@ -227,9 +227,9 @@ func TestEpochState_GetEpochFromTime(t *testing.T) { } type inMemoryBABEData[T any] struct { - epoch uint64 - hashes []common.Hash - data []T + epoch uint64 + hashes []common.Hash + nextData []T } func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { @@ -294,7 +294,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"), common.MustHexToHash("0xc0096358534ec8d21d01d34b836eed476a1c343f8724fa2153dc0725ad797a90"), }, - data: []types.NextEpochData{ + nextData: []types.NextEpochData{ { Authorities: authorities[:3], Randomness: [32]byte{1}, @@ -316,7 +316,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { common.MustHexToHash("0xd380bee22de487a707cbda65dd9d4e2188f736908c42cf390c8919d4f7fc547c"), finalizedHeaderHash, }, - data: []types.NextEpochData{ + nextData: []types.NextEpochData{ { Authorities: authorities[6:], Randomness: [32]byte{1}, @@ -336,7 +336,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { hashes: []common.Hash{ common.MustHexToHash("0xab5c9230a7dde8bb90a6728ba4a0165423294dac14336b1443f865b796ff682c"), }, - data: []types.NextEpochData{ + nextData: []types.NextEpochData{ { Authorities: authorities[6:], Randomness: [32]byte{1}, @@ -359,7 +359,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"), common.MustHexToHash("0xc0096358534ec8d21d01d34b836eed476a1c343f8724fa2153dc0725ad797a90"), }, - data: []types.NextEpochData{ + nextData: []types.NextEpochData{ { Authorities: authorities[:3], Randomness: [32]byte{1}, @@ -391,7 +391,7 @@ func TestStoreAndFinalizeBabeNextEpochData(t *testing.T) { for _, e := range tt.inMemoryEpoch { for i, hash := range e.hashes { - epochState.StoreBABENextEpochData(e.epoch, hash, e.data[i]) + epochState.StoreBABENextEpochData(e.epoch, hash, e.nextData[i]) } } @@ -466,7 +466,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"), common.MustHexToHash("0xc0096358534ec8d21d01d34b836eed476a1c343f8724fa2153dc0725ad797a90"), }, - data: []types.NextConfigData{ + nextData: []types.NextConfigData{ { C1: 1, C2: 2, @@ -491,7 +491,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { common.MustHexToHash("0xd380bee22de487a707cbda65dd9d4e2188f736908c42cf390c8919d4f7fc547c"), finalizedHeaderHash, }, - data: []types.NextConfigData{ + nextData: []types.NextConfigData{ { C1: 1, C2: 2, @@ -514,7 +514,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { hashes: []common.Hash{ common.MustHexToHash("0xab5c9230a7dde8bb90a6728ba4a0165423294dac14336b1443f865b796ff682c"), }, - data: []types.NextConfigData{ + nextData: []types.NextConfigData{ { C1: 1, C2: 2, @@ -537,7 +537,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { common.MustHexToHash("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"), common.MustHexToHash("0xc0096358534ec8d21d01d34b836eed476a1c343f8724fa2153dc0725ad797a90"), }, - data: []types.NextConfigData{ + nextData: []types.NextConfigData{ { C1: 1, C2: 2, @@ -571,7 +571,7 @@ func TestStoreAndFinalizeBabeNextConfigData(t *testing.T) { for _, e := range tt.inMemoryEpoch { for i, hash := range e.hashes { - epochState.StoreBABENextConfigData(e.epoch, hash, e.data[i]) + epochState.StoreBABENextConfigData(e.epoch, hash, e.nextData[i]) } } From 16f35dceedae5a1479a64d9b65a70725e570ce37 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Wed, 13 Apr 2022 14:29:52 -0400 Subject: [PATCH 28/93] chore: update mockery verison --- dot/rpc/modules/mocks/block_api.go | 2 +- dot/rpc/modules/mocks/block_finality_api.go | 2 +- dot/rpc/modules/mocks/block_producer_api.go | 2 +- dot/rpc/modules/mocks/core_api.go | 2 +- dot/rpc/modules/mocks/network_api.go | 2 +- dot/rpc/modules/mocks/rpcapi.go | 2 +- dot/rpc/modules/mocks/runtime_storage_api.go | 2 +- dot/rpc/modules/mocks/storage_api.go | 2 +- dot/rpc/modules/mocks/sync_state_api.go | 2 +- dot/rpc/modules/mocks/system_api.go | 2 +- dot/rpc/modules/mocks/transaction_state_api.go | 2 +- dot/state/mock_observer.go | 2 +- dot/sync/mocks/babe_verifier.go | 2 +- dot/sync/mocks/block_import_handler.go | 2 +- dot/sync/mocks/block_state.go | 2 +- dot/sync/mocks/finality_gadget.go | 2 +- dot/sync/mocks/network.go | 2 +- lib/babe/mocks/block_import_handler.go | 2 +- lib/grandpa/mocks/network.go | 2 +- lib/runtime/mock_memory_test.go | 2 +- lib/runtime/mocks/instance.go | 2 +- lib/runtime/mocks/transaction_state.go | 2 +- lib/runtime/mocks/version.go | 2 +- lib/services/mocks/service.go | 2 +- 24 files changed, 24 insertions(+), 24 deletions(-) diff --git a/dot/rpc/modules/mocks/block_api.go b/dot/rpc/modules/mocks/block_api.go index 8fae0eb002..7fa7736ddb 100644 --- a/dot/rpc/modules/mocks/block_api.go +++ b/dot/rpc/modules/mocks/block_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.6. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/block_finality_api.go b/dot/rpc/modules/mocks/block_finality_api.go index cd163ce5c7..21117ca358 100644 --- a/dot/rpc/modules/mocks/block_finality_api.go +++ b/dot/rpc/modules/mocks/block_finality_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.6. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/block_producer_api.go b/dot/rpc/modules/mocks/block_producer_api.go index a18e2ace04..fd40639138 100644 --- a/dot/rpc/modules/mocks/block_producer_api.go +++ b/dot/rpc/modules/mocks/block_producer_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.6. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/core_api.go b/dot/rpc/modules/mocks/core_api.go index baacb5b5b0..59105fbd01 100644 --- a/dot/rpc/modules/mocks/core_api.go +++ b/dot/rpc/modules/mocks/core_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.6. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/network_api.go b/dot/rpc/modules/mocks/network_api.go index 8ab4e28bec..76871f8469 100644 --- a/dot/rpc/modules/mocks/network_api.go +++ b/dot/rpc/modules/mocks/network_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.6. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/rpcapi.go b/dot/rpc/modules/mocks/rpcapi.go index e5be5e8790..a65beb315e 100644 --- a/dot/rpc/modules/mocks/rpcapi.go +++ b/dot/rpc/modules/mocks/rpcapi.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.6. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/runtime_storage_api.go b/dot/rpc/modules/mocks/runtime_storage_api.go index 0380c1cf82..840c96d059 100644 --- a/dot/rpc/modules/mocks/runtime_storage_api.go +++ b/dot/rpc/modules/mocks/runtime_storage_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.6. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/storage_api.go b/dot/rpc/modules/mocks/storage_api.go index 2b4557ce82..8cb3fbe5bf 100644 --- a/dot/rpc/modules/mocks/storage_api.go +++ b/dot/rpc/modules/mocks/storage_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.6. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/sync_state_api.go b/dot/rpc/modules/mocks/sync_state_api.go index cfbc014ea4..0e9e8c078b 100644 --- a/dot/rpc/modules/mocks/sync_state_api.go +++ b/dot/rpc/modules/mocks/sync_state_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.6. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/system_api.go b/dot/rpc/modules/mocks/system_api.go index f8f39b0c6a..21889736fb 100644 --- a/dot/rpc/modules/mocks/system_api.go +++ b/dot/rpc/modules/mocks/system_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.6. DO NOT EDIT. package mocks diff --git a/dot/rpc/modules/mocks/transaction_state_api.go b/dot/rpc/modules/mocks/transaction_state_api.go index d783820fa4..82fc6e7553 100644 --- a/dot/rpc/modules/mocks/transaction_state_api.go +++ b/dot/rpc/modules/mocks/transaction_state_api.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.6. DO NOT EDIT. package mocks diff --git a/dot/state/mock_observer.go b/dot/state/mock_observer.go index 6a0683586d..dfdb42adcc 100644 --- a/dot/state/mock_observer.go +++ b/dot/state/mock_observer.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.6. DO NOT EDIT. package state diff --git a/dot/sync/mocks/babe_verifier.go b/dot/sync/mocks/babe_verifier.go index 0be36b3ad8..3b11bbf29d 100644 --- a/dot/sync/mocks/babe_verifier.go +++ b/dot/sync/mocks/babe_verifier.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.6. DO NOT EDIT. package mocks diff --git a/dot/sync/mocks/block_import_handler.go b/dot/sync/mocks/block_import_handler.go index 10488e6f7a..6b4cd46b3f 100644 --- a/dot/sync/mocks/block_import_handler.go +++ b/dot/sync/mocks/block_import_handler.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.6. DO NOT EDIT. package mocks diff --git a/dot/sync/mocks/block_state.go b/dot/sync/mocks/block_state.go index 367601d550..2f9d48d9af 100644 --- a/dot/sync/mocks/block_state.go +++ b/dot/sync/mocks/block_state.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.6. DO NOT EDIT. package mocks diff --git a/dot/sync/mocks/finality_gadget.go b/dot/sync/mocks/finality_gadget.go index f3c1fc3e5e..6179f5d6fd 100644 --- a/dot/sync/mocks/finality_gadget.go +++ b/dot/sync/mocks/finality_gadget.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.6. DO NOT EDIT. package mocks diff --git a/dot/sync/mocks/network.go b/dot/sync/mocks/network.go index d1b945bde6..d9c8accaf5 100644 --- a/dot/sync/mocks/network.go +++ b/dot/sync/mocks/network.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.6. DO NOT EDIT. package mocks diff --git a/lib/babe/mocks/block_import_handler.go b/lib/babe/mocks/block_import_handler.go index c803e1f65b..c4c61cf002 100644 --- a/lib/babe/mocks/block_import_handler.go +++ b/lib/babe/mocks/block_import_handler.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.6. DO NOT EDIT. package mocks diff --git a/lib/grandpa/mocks/network.go b/lib/grandpa/mocks/network.go index a1930740db..5d7e607e03 100644 --- a/lib/grandpa/mocks/network.go +++ b/lib/grandpa/mocks/network.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.6. DO NOT EDIT. package mocks diff --git a/lib/runtime/mock_memory_test.go b/lib/runtime/mock_memory_test.go index 97374c51cc..9fca62f217 100644 --- a/lib/runtime/mock_memory_test.go +++ b/lib/runtime/mock_memory_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.6. DO NOT EDIT. package runtime diff --git a/lib/runtime/mocks/instance.go b/lib/runtime/mocks/instance.go index 9188ba2349..c8f2353909 100644 --- a/lib/runtime/mocks/instance.go +++ b/lib/runtime/mocks/instance.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.6. DO NOT EDIT. package mocks diff --git a/lib/runtime/mocks/transaction_state.go b/lib/runtime/mocks/transaction_state.go index 952ecfad4e..f7dd59036c 100644 --- a/lib/runtime/mocks/transaction_state.go +++ b/lib/runtime/mocks/transaction_state.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.6. DO NOT EDIT. package mocks diff --git a/lib/runtime/mocks/version.go b/lib/runtime/mocks/version.go index b389875077..1999794717 100644 --- a/lib/runtime/mocks/version.go +++ b/lib/runtime/mocks/version.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.6. DO NOT EDIT. package mocks diff --git a/lib/services/mocks/service.go b/lib/services/mocks/service.go index 98752d2740..d6af8c0324 100644 --- a/lib/services/mocks/service.go +++ b/lib/services/mocks/service.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.6. DO NOT EDIT. package mocks From 1149c1f093bc85dd495069d14a95eb748393ba98 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Wed, 13 Apr 2022 14:45:28 -0400 Subject: [PATCH 29/93] chore: update devnet mocks as well --- devnet/cmd/scale-down-ecs-service/mocks/ecsapi.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devnet/cmd/scale-down-ecs-service/mocks/ecsapi.go b/devnet/cmd/scale-down-ecs-service/mocks/ecsapi.go index b77faa9dd9..c3dd446d7a 100644 --- a/devnet/cmd/scale-down-ecs-service/mocks/ecsapi.go +++ b/devnet/cmd/scale-down-ecs-service/mocks/ecsapi.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.10.4. DO NOT EDIT. +// Code generated by mockery v2.10.6. DO NOT EDIT. package mocks From 7ce6a1c946e8fc39b3389ef6c4404c0f9510d89a Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Thu, 14 Apr 2022 17:16:33 -0400 Subject: [PATCH 30/93] chore: change name to be more explicit --- dot/state/grandpa.go | 97 +++++++++++++++----------------------------- 1 file changed, 33 insertions(+), 64 deletions(-) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 089dc26462..937cd1888d 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -251,25 +251,24 @@ func (s *GrandpaState) importScheduledChange(header *types.Header, pendingChange return nil } -// finalizedScheduledChange iterates throught the scheduled change tree roots looking for the change node, which +// getApplicableChange iterates throught the scheduled change tree roots looking for the change node, which // contains a lower or equal effective number, to apply. When we found that node we update the scheduled change tree roots // with its children that belongs to the same finalized node branch. If we don't find such node we update the scheduled // change tree roots with the change nodes that belongs to the same finalized node branch -func (s *GrandpaState) finalizedScheduledChange(finalizedHeader *types.Header) (apply bool, change *pendingChange, err error) { +func (s *GrandpaState) getApplicableChange(finalizedHash common.Hash, finalizedNumber uint) (change *pendingChange, err error) { bestFinalizedHeader, err := s.blockState.GetHighestFinalisedHeader() if err != nil { - return false, nil, fmt.Errorf("cannot get highest finalised header: %w", err) + return nil, fmt.Errorf("cannot get highest finalised header: %w", err) } - if finalizedHeader.Number <= bestFinalizedHeader.Number { - return false, nil, ErrLowerThanBestFinalized + if finalizedNumber < bestFinalizedHeader.Number { + return nil, ErrLowerThanBestFinalized } effectiveBlockLowerOrEqualFinalized := func(change *pendingChange) bool { - return change.effectiveNumber() <= finalizedHeader.Number + return change.effectiveNumber() <= finalizedNumber } - finalizedHash := finalizedHeader.Hash() position := -1 for idx, root := range s.scheduledChangeRoots { if !effectiveBlockLowerOrEqualFinalized(root.change) { @@ -283,7 +282,7 @@ func (s *GrandpaState) finalizedScheduledChange(finalizedHeader *types.Header) ( if !finalizedHash.Equal(rootHash) { isDescendant, err := s.blockState.IsDescendantOf(rootHash, finalizedHash) if err != nil { - return false, nil, fmt.Errorf("cannot verify ancestry: %w", err) + return nil, fmt.Errorf("cannot verify ancestry: %w", err) } if !isDescendant { @@ -296,11 +295,11 @@ func (s *GrandpaState) finalizedScheduledChange(finalizedHeader *types.Header) ( for _, node := range root.nodes { isDescendant, err := s.blockState.IsDescendantOf(node.header.Hash(), finalizedHash) if err != nil { - return false, nil, fmt.Errorf("cannot verify ancestry: %w", err) + return nil, fmt.Errorf("cannot verify ancestry: %w", err) } - if node.header.Number <= finalizedHeader.Number && isDescendant { - return false, nil, ErrUnfinalizedAncestor + if node.header.Number <= finalizedNumber && isDescendant { + return nil, ErrUnfinalizedAncestor } } @@ -318,81 +317,51 @@ func (s *GrandpaState) finalizedScheduledChange(finalizedHeader *types.Header) ( copy(s.scheduledChangeRoots, pendingChangeNodeAtPosition.nodes) } - changed, err := s.retainScheduledChangeRoots(finalizedHeader) - if err != nil { - return false, nil, fmt.Errorf("cannot retain the scheduled change roots: %w", err) - } - - apply = changeToApply != nil || changed - return apply, changeToApply, nil + return changeToApply, nil } -// retainScheduledChangeRoots keeps the child nodes from the applied standard change as the new roots of the -// s.scheduledChanges tree -func (s *GrandpaState) retainScheduledChangeRoots(finalizedHeader *types.Header) (changed bool, err error) { - newScheduledChangeRoots := []*pendingChangeNode{} - finalizedHash := finalizedHeader.Hash() - - for _, root := range s.scheduledChangeRoots { - rootBlockHash := root.header.Hash() - - isAncestor, err := s.blockState.IsDescendantOf(finalizedHash, rootBlockHash) - if err != nil { - return false, fmt.Errorf("cannot verify ancestry while ancestor: %w", err) - } +// keepDescendantForcedChanges should keep the forced changes for later blocks that +// are descendant of the finalized block +func (s *GrandpaState) keepDescendantForcedChanges(finalizedHash common.Hash, finalizedNumber uint) error { + onBranchForcedChanges := []*pendingChange{} - isDescendant, err := s.blockState.IsDescendantOf(rootBlockHash, finalizedHash) + for _, forcedChange := range s.forcedChanges { + isDescendant, err := s.blockState.IsDescendantOf(finalizedHash, forcedChange.announcingHeader.Hash()) if err != nil { - return false, fmt.Errorf("cannot verify ancestry while descendant: %w", err) + return fmt.Errorf("cannot verify ancestry while ancestor: %w", err) } - retain := root.header.Number > finalizedHeader.Number && isAncestor || - root.header.Number == finalizedHeader.Number && finalizedHash.Equal(rootBlockHash) || - isDescendant - - if retain { - newScheduledChangeRoots = append(newScheduledChangeRoots, root) - } else { - changed = true + if forcedChange.effectiveNumber() > finalizedNumber && isDescendant { + onBranchForcedChanges = append(onBranchForcedChanges, forcedChange) } } - s.scheduledChangeRoots = make([]*pendingChangeNode, len(newScheduledChangeRoots)) - copy(s.scheduledChangeRoots, newScheduledChangeRoots) - return changed, nil + s.forcedChanges = make(orderedPendingChanges, len(onBranchForcedChanges)) + copy(s.forcedChanges, onBranchForcedChanges) + + return nil } // ApplyScheduledChange will check the schedules changes in order to find a root // that is equals or behind the finalized number and will apply its authority set changes func (s *GrandpaState) ApplyScheduledChanges(finalizedHeader *types.Header) error { - changed, changeToApply, err := s.finalizedScheduledChange(finalizedHeader) + finalizedHash := finalizedHeader.Hash() + + err := s.keepDescendantForcedChanges(finalizedHash, finalizedHeader.Number) if err != nil { - return fmt.Errorf("cannot finalize scheduled change: %w", err) + return fmt.Errorf("cannot keep descendant forced changes: %w", err) } - logger.Debugf("finalized scheduled change: changes: %v, change to apply: %s", changed, changeToApply) - if !changed { + if len(s.scheduledChangeRoots) == 0 { return nil } - finalizedHash := finalizedHeader.Hash() - onBranchForcedChanges := []*pendingChange{} - - // we should keep the forced changes for later blocks that - // are descendant of the finalized block - for _, forcedChange := range s.forcedChanges { - isDescendant, err := s.blockState.IsDescendantOf(finalizedHash, forcedChange.announcingHeader.Hash()) - if err != nil { - return fmt.Errorf("cannot verify ancestry while ancestor: %w", err) - } - - if forcedChange.effectiveNumber() > finalizedHeader.Number && isDescendant { - onBranchForcedChanges = append(onBranchForcedChanges, forcedChange) - } + changeToApply, err := s.getApplicableChange(finalizedHash, finalizedHeader.Number) + if err != nil { + return fmt.Errorf("cannot finalize scheduled change: %w", err) } - s.forcedChanges = make(orderedPendingChanges, len(onBranchForcedChanges)) - copy(s.forcedChanges, onBranchForcedChanges) + logger.Debugf("scheduled changes: change to apply: %s", changeToApply) if changeToApply != nil { newSetID, err := s.IncrementSetID() From 400982432b4b8aded1c812d340d2afa8cba9ba8b Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Sun, 17 Apr 2022 18:43:21 +0200 Subject: [PATCH 31/93] wip --- dot/digest/digest.go | 22 +++------------------- dot/state/grandpa.go | 6 +++++- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/dot/digest/digest.go b/dot/digest/digest.go index 5a217e3786..eeeb631221 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -36,28 +36,9 @@ type Handler struct { imported chan *types.Block finalised chan *types.FinalisationInfo - // GRANDPA changes - grandpaScheduledChange *grandpaChange - grandpaForcedChange *grandpaChange - grandpaPause *pause - grandpaResume *resume - logger log.LeveledLogger } -type grandpaChange struct { - auths []types.Authority - atBlock uint -} - -type pause struct { - atBlock uint -} - -type resume struct { - atBlock uint -} - // NewHandler returns a new Handler func NewHandler(lvl log.Level, blockState BlockState, epochState EpochState, grandpaState GrandpaState) (*Handler, error) { @@ -179,6 +160,9 @@ func (h *Handler) handleBlockImport(ctx context.Context) { } h.HandleDigests(&block.Header) + + h.grandpaState.ApplyForcedChanges(&block.Header) + case <-ctx.Done(): return } diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 937cd1888d..e2b5f0f0b9 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -309,7 +309,7 @@ func (s *GrandpaState) getApplicableChange(finalizedHash common.Hash, finalizedN var changeToApply *pendingChange = nil - if position != -1 { + if position > -1 { pendingChangeNodeAtPosition := s.scheduledChangeRoots[position] changeToApply = pendingChangeNodeAtPosition.change @@ -435,6 +435,10 @@ func (s *GrandpaState) scheduledChangeOnChainOf(bestBlockHash common.Hash) (chan return nil, nil } +func (s *GrandpaState) ApplyForcedChanges(bestBlockHeader *types.Header) error { + return nil +} + // NextGrandpaAuthorityChange returns the block number of the next upcoming grandpa authorities change. // It returns 0 if no change is scheduled. func (s *GrandpaState) NextGrandpaAuthorityChange(bestBlockHash common.Hash) (blockNumber uint, err error) { From bf9315fbcc7d9a47c5b322e724c6e334361481f3 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Mon, 18 Apr 2022 18:45:23 +0200 Subject: [PATCH 32/93] chore: wip --- dot/digest/digest.go | 3 +-- dot/digest/digest_test.go | 2 -- dot/state/grandpa.go | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/dot/digest/digest.go b/dot/digest/digest.go index eeeb631221..57bb28f841 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -161,8 +161,7 @@ func (h *Handler) handleBlockImport(ctx context.Context) { h.HandleDigests(&block.Header) - h.grandpaState.ApplyForcedChanges(&block.Header) - + //h.grandpaState.ApplyForcedChanges(&block.Header) case <-ctx.Done(): return } diff --git a/dot/digest/digest_test.go b/dot/digest/digest_test.go index 296535bfc2..3424202a8a 100644 --- a/dot/digest/digest_test.go +++ b/dot/digest/digest_test.go @@ -198,7 +198,6 @@ func TestHandler_GrandpaPauseAndResume(t *testing.T) { } time.Sleep(time.Millisecond * 100) - require.Nil(t, handler.grandpaPause) r := types.GrandpaResume{ Delay: 3, @@ -221,7 +220,6 @@ func TestHandler_GrandpaPauseAndResume(t *testing.T) { state.AddBlocksToState(t, handler.blockState.(*state.BlockState), 3, false) time.Sleep(time.Millisecond * 110) - require.Nil(t, handler.grandpaResume) nextResume, err := handler.grandpaState.(*state.GrandpaState).GetNextResume() require.NoError(t, err) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index e2b5f0f0b9..5f72145417 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -207,7 +207,7 @@ func (s *GrandpaState) addForcedChange(header *types.Header, fc types.GrandpaFor func (s *GrandpaState) addScheduledChange(header *types.Header, sc types.GrandpaScheduledChange) error { auths, err := types.GrandpaAuthoritiesRawToAuthorities(sc.Auths) if err != nil { - return fmt.Errorf("cannot parser GRANPDA authorities to raw authorities: %w", err) + return fmt.Errorf("cannot parse GRANPDA authorities to raw authorities: %w", err) } pendingChange := &pendingChange{ From 869641c26aee3ac52ce87aa606d8aa984212729b Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Sun, 24 Apr 2022 13:58:00 +0200 Subject: [PATCH 33/93] chore: wip writing unit tests --- dot/digest/digest.go | 2 +- dot/digest/interface.go | 1 + dot/state/grandpa.go | 111 +++++++++++++++++-- dot/state/grandpa_test.go | 198 +++++++++++++++++++++------------- dot/types/consensus_digest.go | 5 +- 5 files changed, 232 insertions(+), 85 deletions(-) diff --git a/dot/digest/digest.go b/dot/digest/digest.go index 57bb28f841..11a00415ae 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -161,7 +161,7 @@ func (h *Handler) handleBlockImport(ctx context.Context) { h.HandleDigests(&block.Header) - //h.grandpaState.ApplyForcedChanges(&block.Header) + h.grandpaState.ApplyForcedChanges(&block.Header) case <-ctx.Done(): return } diff --git a/dot/digest/interface.go b/dot/digest/interface.go index b0fe708986..4252884915 100644 --- a/dot/digest/interface.go +++ b/dot/digest/interface.go @@ -43,4 +43,5 @@ type GrandpaState interface { HandleGRANDPADigest(header *types.Header, digest scale.VaryingDataType) error ApplyScheduledChanges(finalizedheader *types.Header) error + ApplyForcedChanges(importedHeader *types.Header) error } diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 5f72145417..9ab68e159f 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -34,9 +34,10 @@ var ( ) type pendingChange struct { - delay uint32 - nextAuthorities []types.Authority - announcingHeader *types.Header + bestFinalizedNumber uint32 + delay uint32 + nextAuthorities []types.Authority + announcingHeader *types.Header } func (p pendingChange) String() string { @@ -193,9 +194,10 @@ func (s *GrandpaState) addForcedChange(header *types.Header, fc types.GrandpaFor } pendingChange := &pendingChange{ - nextAuthorities: auths, - announcingHeader: header, - delay: fc.Delay, + bestFinalizedNumber: fc.BestFinalizedBlock, + nextAuthorities: auths, + announcingHeader: header, + delay: fc.Delay, } s.forcedChanges = append(s.forcedChanges, pendingChange) @@ -389,6 +391,99 @@ func (s *GrandpaState) ApplyScheduledChanges(finalizedHeader *types.Header) erro return nil } +// ApplyForcedChanges will check for if there is a scheduled forced change relative to the +// imported block and then apply it otherwise nothing happens +func (s *GrandpaState) ApplyForcedChanges(importedBlockHeader *types.Header) error { + importedHash := importedBlockHeader.Hash() + + var forcedChange *pendingChange + + for _, forced := range s.forcedChanges { + announcingHash := forcedChange.announcingHeader.Hash() + effectiveNumber := forcedChange.effectiveNumber() + + if importedHash.Equal(announcingHash) && effectiveNumber == 0 { + forcedChange = forced + break + } + + isDescendant, err := s.blockState.IsDescendantOf(announcingHash, importedHash) + if err != nil { + return fmt.Errorf("cannot check ancestry: %w", err) + } + + if !isDescendant { + continue + } + + if effectiveNumber == importedBlockHeader.Number { + forcedChange = forced + break + } + } + + if forcedChange == nil { + return nil + } + + forcedChangeHash := forcedChange.announcingHeader.Hash() + + // checking for dependant pending scheduled changes + for _, scheduled := range s.scheduledChangeRoots { + bestFinalizedNumber := forcedChange.bestFinalizedNumber + if scheduled.change.effectiveNumber() > uint(bestFinalizedNumber) { + continue + } + + scheduledHash := scheduled.change.announcingHeader.Hash() + isDescendant, err := s.blockState.IsDescendantOf(scheduledHash, forcedChangeHash) + if err != nil { + return fmt.Errorf("cannot check ancestry: %w", err) + } + + if isDescendant { + return fmt.Errorf( + "not authority set change forced at block #%d, due to pending standard change at block #%d", + forcedChange.announcingHeader.Number, + scheduled.change.effectiveNumber()) + } + } + + // send the telemetry s messages here + // afg.applying_forced_authority_set_change + + currentSetID, err := s.GetCurrentSetID() + if err != nil { + return fmt.Errorf("cannot get current set id: %w", err) + } + + err = s.setChangeSetIDAtBlock(currentSetID, uint(forcedChange.bestFinalizedNumber)) + if err != nil { + return fmt.Errorf("cannot set change set id at block: %w", err) + } + + newSetID, err := s.IncrementSetID() + if err != nil { + return fmt.Errorf("cannot increment set id: %w", err) + } + + grandpaVotersAuthorities := types.NewGrandpaVotersFromAuthorities(forcedChange.nextAuthorities) + err = s.setAuthorities(newSetID, grandpaVotersAuthorities) + if err != nil { + return fmt.Errorf("cannot set authorities: %w", err) + } + + err = s.setChangeSetIDAtBlock(newSetID, forcedChange.effectiveNumber()) + if err != nil { + return fmt.Errorf("cannot set change set id at block") + } + + logger.Debugf("Applying authority set forced change at block #%d", + forcedChange.announcingHeader.Number) + + return nil +} + // forcedChangeOnChain walk through the forced change slice looking for // a forced change that belong to the same branch as bestBlockHash parameter func (s *GrandpaState) forcedChangeOnChain(bestBlockHash common.Hash) (change *pendingChange, err error) { @@ -435,10 +530,6 @@ func (s *GrandpaState) scheduledChangeOnChainOf(bestBlockHash common.Hash) (chan return nil, nil } -func (s *GrandpaState) ApplyForcedChanges(bestBlockHeader *types.Header) error { - return nil -} - // NextGrandpaAuthorityChange returns the block number of the next upcoming grandpa authorities change. // It returns 0 if no change is scheduled. func (s *GrandpaState) NextGrandpaAuthorityChange(bestBlockHash common.Hash) (blockNumber uint, err error) { diff --git a/dot/state/grandpa_test.go b/dot/state/grandpa_test.go index b8fbf3527c..1d8b7bbcc6 100644 --- a/dot/state/grandpa_test.go +++ b/dot/state/grandpa_test.go @@ -4,14 +4,18 @@ package state import ( + "fmt" "testing" "github.com/ChainSafe/chaindb" "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/crypto" "github.com/ChainSafe/gossamer/lib/crypto/ed25519" + "github.com/ChainSafe/gossamer/lib/crypto/sr25519" "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/lib/trie" "github.com/golang/mock/gomock" + "github.com/gtank/merlin" "github.com/stretchr/testify/require" ) @@ -137,97 +141,147 @@ func testBlockState(t *testing.T, db chaindb.Database) *BlockState { return bs } -// func TestImportGrandpaChangesKeepDecreasingOrdered(t *testing.T) { -// keyring, err := keystore.NewSr25519Keyring() -// require.NoError(t, err) +func TestForcedScheduledChangesOrder(t *testing.T) { + keyring, err := keystore.NewSr25519Keyring() + require.NoError(t, err) -// db := NewInMemoryDB(t) -// blockState := testBlockState(t, db) + db := NewInMemoryDB(t) + blockState := testBlockState(t, db) -// gs, err := NewGrandpaStateFromGenesis(db, blockState, nil) -// require.NoError(t, err) + gs, err := NewGrandpaStateFromGenesis(db, blockState, nil) + require.NoError(t, err) -// scheduledChanges := types.GrandpaScheduledChange{} -// forkChainA := issueBlocksWithGRANDPAScheduledChanges(t, keyring.KeyAlice, blockState, -// testGenesisHeader, 3) + aliceHeaders := issueBlocksWithBABEPrimary(t, keyring.KeyAlice, gs.blockState, + testGenesisHeader, 5) + + bobHeaders := issueBlocksWithBABEPrimary(t, keyring.KeyBob, gs.blockState, + aliceHeaders[1], 5) + + charlieHeaders := issueBlocksWithBABEPrimary(t, keyring.KeyCharlie, gs.blockState, + aliceHeaders[2], 6) + + forcedChanges := map[*types.Header]types.GrandpaForcedChange{ + bobHeaders[1]: { + Delay: 1, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyAlice.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }, + aliceHeaders[3]: { + Delay: 5, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyAlice.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }, + charlieHeaders[4]: { + Delay: 3, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyAlice.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }, + } -// forkChainA = shuffleHeaderSlice(forkChainA) + for header, fc := range forcedChanges { + err := gs.addForcedChange(header, fc) + require.NoError(t, err, "failed to add forced change") + } -// for _, header := range forkChainA { -// grandpaConsensusDigest := types.NewGrandpaConsensusDigest() -// err := grandpaConsensusDigest.Set(scheduledChanges) -// require.NoError(t, err) + for _, f := range gs.forcedChanges { + fmt.Printf("HEADER NUMBER: %d, EFFECTIVE: %d\n", f.announcingHeader.Number, f.effectiveNumber()) + } +} + +func headerWithBABEPrimaryPreDigest(t *testing.T, kp *sr25519.Keypair, bs *BlockState, + parentHeader *types.Header) (header *types.Header) { + t.Helper() + + transcript := merlin.NewTranscript("BABE") //string(types.BabeEngineID[:]) + crypto.AppendUint64(transcript, []byte("slot number"), 1) + crypto.AppendUint64(transcript, []byte("current epoch"), 1) + transcript.AppendMessage([]byte("chain randomness"), []byte{}) + + output, proof, err := kp.VrfSign(transcript) + require.NoError(t, err) + + babePrimaryPreDigest := types.BabePrimaryPreDigest{ + SlotNumber: 1, + VRFOutput: output, + VRFProof: proof, + } -// err = gs.ImportGrandpaChange(header, grandpaConsensusDigest) -// require.NoError(t, err) -// } + preRuntimeDigest, err := babePrimaryPreDigest.ToPreRuntimeDigest() + require.NoError(t, err) -// require.Len(t, gs.forks, 1) + digest := types.NewDigest() -// forkAStartHash := forkChainA[0].Hash() -// linkedList := gs.forks[forkAStartHash] + require.NoError(t, digest.Add(*preRuntimeDigest)) + header = &types.Header{ + ParentHash: parentHeader.Hash(), + Number: parentHeader.Number + 1, + Digest: digest, + } -// for linkedList.Next != nil { -// require.Greater(t, -// linkedList.header.Number, linkedList.Next.header.Number) -// linkedList = linkedList.Next -// } + block := &types.Block{ + Header: *header, + Body: *types.NewBody([]types.Extrinsic{}), + } -// fmt.Println() -// } + err = bs.AddBlock(block) + require.NoError(t, err) -// func issueBlocksWithGRANDPAScheduledChanges(t *testing.T, kp *sr25519.Keypair, -// bs *BlockState, parentHeader *types.Header, size int) (headers []*types.Header) { -// t.Helper() + return header +} -// transcript := merlin.NewTranscript("BABE") //string(types.BabeEngineID[:]) -// crypto.AppendUint64(transcript, []byte("slot number"), 1) -// crypto.AppendUint64(transcript, []byte("current epoch"), 1) -// transcript.AppendMessage([]byte("chain randomness"), []byte{}) +func issueBlocksWithBABEPrimary(t *testing.T, kp *sr25519.Keypair, + bs *BlockState, parentHeader *types.Header, size int) (headers []*types.Header) { + t.Helper() -// output, proof, err := kp.VrfSign(transcript) -// require.NoError(t, err) + transcript := merlin.NewTranscript("BABE") //string(types.BabeEngineID[:]) + crypto.AppendUint64(transcript, []byte("slot number"), 1) + crypto.AppendUint64(transcript, []byte("current epoch"), 1) + transcript.AppendMessage([]byte("chain randomness"), []byte{}) -// babePrimaryPreDigest := types.BabePrimaryPreDigest{ -// SlotNumber: 1, -// VRFOutput: output, -// VRFProof: proof, -// } + output, proof, err := kp.VrfSign(transcript) + require.NoError(t, err) -// preRuntimeDigest, err := babePrimaryPreDigest.ToPreRuntimeDigest() -// require.NoError(t, err) + babePrimaryPreDigest := types.BabePrimaryPreDigest{ + SlotNumber: 1, + VRFOutput: output, + VRFProof: proof, + } -// digest := types.NewDigest() + preRuntimeDigest, err := babePrimaryPreDigest.ToPreRuntimeDigest() + require.NoError(t, err) -// require.NoError(t, digest.Add(*preRuntimeDigest)) -// header := &types.Header{ -// ParentHash: parentHeader.Hash(), -// Number: parentHeader.Number + 1, -// Digest: digest, -// } + digest := types.NewDigest() -// block := &types.Block{ -// Header: *header, -// Body: *types.NewBody([]types.Extrinsic{}), -// } + require.NoError(t, digest.Add(*preRuntimeDigest)) + header := &types.Header{ + ParentHash: parentHeader.Hash(), + Number: parentHeader.Number + 1, + Digest: digest, + } -// err = bs.AddBlock(block) -// require.NoError(t, err) + block := &types.Block{ + Header: *header, + Body: *types.NewBody([]types.Extrinsic{}), + } -// if size <= 0 { -// headers = append(headers, header) -// return headers -// } + err = bs.AddBlock(block) + require.NoError(t, err) -// headers = append(headers, header) -// headers = append(headers, issueBlocksWithGRANDPAScheduledChanges(t, kp, bs, header, size-1)...) -// return headers -// } + if size <= 0 { + headers = append(headers, header) + return headers + } -// func shuffleHeaderSlice(headers []*types.Header) []*types.Header { -// rand.Seed(time.Now().UnixNano()) -// rand.Shuffle(len(headers), func(i, j int) { -// headers[i], headers[j] = headers[j], headers[i] -// }) -// return headers -// } + headers = append(headers, header) + headers = append(headers, issueBlocksWithBABEPrimary(t, kp, bs, header, size-1)...) + return headers +} diff --git a/dot/types/consensus_digest.go b/dot/types/consensus_digest.go index 0d704e964d..9cf8e59a96 100644 --- a/dot/types/consensus_digest.go +++ b/dot/types/consensus_digest.go @@ -31,8 +31,9 @@ func (sc GrandpaScheduledChange) Index() uint { return 1 } // GrandpaForcedChange represents a GRANDPA forced authority change type GrandpaForcedChange struct { - Auths []GrandpaAuthoritiesRaw - Delay uint32 + BestFinalizedBlock uint32 + Auths []GrandpaAuthoritiesRaw + Delay uint32 } // Index Returns VDT index From d03066735488505e2d865b70c03fbc5f1b5513b3 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Mon, 25 Apr 2022 11:01:23 +0200 Subject: [PATCH 34/93] chore: adding tests --- dot/state/grandpa.go | 7 ++-- dot/state/grandpa_test.go | 69 +++++++++++++++++++++------------------ 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 9ab68e159f..de480323eb 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -27,6 +27,7 @@ var ( ) var ( + ErrDuplicatedHashes = errors.New("duplicated hashes") ErrAlreadyHasForcedChanges = errors.New("already has a forced change") ErrUnfinalizedAncestor = errors.New("ancestor with changes not applied") ErrNoChanges = errors.New("cannot get the next authority change block number") @@ -175,7 +176,7 @@ func (s *GrandpaState) addForcedChange(header *types.Header, fc types.GrandpaFor changeBlockHash := change.announcingHeader.Hash() if changeBlockHash == headerHash { - return errors.New("duplicated hash") + return ErrDuplicatedHashes } isDescendant, err := s.blockState.IsDescendantOf(changeBlockHash, headerHash) @@ -184,13 +185,13 @@ func (s *GrandpaState) addForcedChange(header *types.Header, fc types.GrandpaFor } if isDescendant { - return errors.New("multiple forced changes") + return ErrAlreadyHasForcedChanges } } auths, err := types.GrandpaAuthoritiesRawToAuthorities(fc.Auths) if err != nil { - return fmt.Errorf("cannot parser GRANPDA authorities to raw authorities: %w", err) + return fmt.Errorf("cannot parser GRANDPA authorities to raw authorities: %w", err) } pendingChange := &pendingChange{ diff --git a/dot/state/grandpa_test.go b/dot/state/grandpa_test.go index 1d8b7bbcc6..7a75327f98 100644 --- a/dot/state/grandpa_test.go +++ b/dot/state/grandpa_test.go @@ -4,7 +4,6 @@ package state import ( - "fmt" "testing" "github.com/ChainSafe/chaindb" @@ -192,50 +191,58 @@ func TestForcedScheduledChangesOrder(t *testing.T) { require.NoError(t, err, "failed to add forced change") } - for _, f := range gs.forcedChanges { - fmt.Printf("HEADER NUMBER: %d, EFFECTIVE: %d\n", f.announcingHeader.Number, f.effectiveNumber()) - } -} + for idx := 0; idx < len(gs.forcedChanges)-1; idx++ { + currentChange := gs.forcedChanges[idx] + nextChange := gs.forcedChanges[idx+1] -func headerWithBABEPrimaryPreDigest(t *testing.T, kp *sr25519.Keypair, bs *BlockState, - parentHeader *types.Header) (header *types.Header) { - t.Helper() + require.LessOrEqual(t, currentChange.effectiveNumber(), + nextChange.effectiveNumber()) - transcript := merlin.NewTranscript("BABE") //string(types.BabeEngineID[:]) - crypto.AppendUint64(transcript, []byte("slot number"), 1) - crypto.AppendUint64(transcript, []byte("current epoch"), 1) - transcript.AppendMessage([]byte("chain randomness"), []byte{}) + require.LessOrEqual(t, currentChange.announcingHeader.Number, + nextChange.announcingHeader.Number) + } +} - output, proof, err := kp.VrfSign(transcript) +func TestShouldNotAddMoreThanOneForcedChangeInTheSameFork(t *testing.T) { + keyring, err := keystore.NewSr25519Keyring() require.NoError(t, err) - babePrimaryPreDigest := types.BabePrimaryPreDigest{ - SlotNumber: 1, - VRFOutput: output, - VRFProof: proof, - } + db := NewInMemoryDB(t) + blockState := testBlockState(t, db) - preRuntimeDigest, err := babePrimaryPreDigest.ToPreRuntimeDigest() + gs, err := NewGrandpaStateFromGenesis(db, blockState, nil) require.NoError(t, err) - digest := types.NewDigest() + aliceHeaders := issueBlocksWithBABEPrimary(t, keyring.KeyAlice, gs.blockState, + testGenesisHeader, 5) - require.NoError(t, digest.Add(*preRuntimeDigest)) - header = &types.Header{ - ParentHash: parentHeader.Hash(), - Number: parentHeader.Number + 1, - Digest: digest, - } + bobHeaders := issueBlocksWithBABEPrimary(t, keyring.KeyBob, gs.blockState, + aliceHeaders[1], 5) - block := &types.Block{ - Header: *header, - Body: *types.NewBody([]types.Extrinsic{}), + someForcedChange := types.GrandpaForcedChange{ + Delay: 1, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyAlice.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + }, } - err = bs.AddBlock(block) + // adding more than one forced changes in the same branch + err = gs.addForcedChange(aliceHeaders[3], someForcedChange) + require.NoError(t, err) + + err = gs.addForcedChange(aliceHeaders[4], someForcedChange) + require.Error(t, err) + require.ErrorIs(t, err, ErrAlreadyHasForcedChanges) + + // adding the same forced change twice + err = gs.addForcedChange(bobHeaders[2], someForcedChange) require.NoError(t, err) - return header + err = gs.addForcedChange(bobHeaders[2], someForcedChange) + require.Error(t, err) + require.ErrorIs(t, err, ErrDuplicatedHashes) } func issueBlocksWithBABEPrimary(t *testing.T, kp *sr25519.Keypair, From a9dfa6b4c72c1a8ff259bbbc1dec18770d6450fa Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Mon, 25 Apr 2022 15:39:18 +0200 Subject: [PATCH 35/93] chore: improve message --- dot/digest/digest.go | 1 - dot/state/grandpa.go | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/dot/digest/digest.go b/dot/digest/digest.go index 11a00415ae..faa7812214 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -160,7 +160,6 @@ func (h *Handler) handleBlockImport(ctx context.Context) { } h.HandleDigests(&block.Header) - h.grandpaState.ApplyForcedChanges(&block.Header) case <-ctx.Done(): return diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index de480323eb..8c5da605aa 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -396,7 +396,6 @@ func (s *GrandpaState) ApplyScheduledChanges(finalizedHeader *types.Header) erro // imported block and then apply it otherwise nothing happens func (s *GrandpaState) ApplyForcedChanges(importedBlockHeader *types.Header) error { importedHash := importedBlockHeader.Hash() - var forcedChange *pendingChange for _, forced := range s.forcedChanges { @@ -444,7 +443,7 @@ func (s *GrandpaState) ApplyForcedChanges(importedBlockHeader *types.Header) err if isDescendant { return fmt.Errorf( - "not authority set change forced at block #%d, due to pending standard change at block #%d", + "forced authority fails at block #%d due to pending standard change at block #%d", forcedChange.announcingHeader.Number, scheduled.change.effectiveNumber()) } From 5fcc89acc8123d175e48b30fd7b92e2ec0bb6cd2 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Wed, 27 Apr 2022 15:22:47 -0400 Subject: [PATCH 36/93] chore: wip tersts --- dot/digest/digest_test.go | 1 - dot/state/grandpa.go | 96 ++++++++++++++----------- dot/state/grandpa_test.go | 144 +++++++++++++++++++++++++++++++++++++- 3 files changed, 198 insertions(+), 43 deletions(-) diff --git a/dot/digest/digest_test.go b/dot/digest/digest_test.go index 3424202a8a..6fe8995b38 100644 --- a/dot/digest/digest_test.go +++ b/dot/digest/digest_test.go @@ -226,7 +226,6 @@ func TestHandler_GrandpaPauseAndResume(t *testing.T) { require.Equal(t, uint(r.Delay+p.Delay), nextResume) } -// TODO: move this test to dot/state/grandpa.go // func TestNextGrandpaAuthorityChange_OneChange(t *testing.T) { // handler, _ := newTestHandler(t) // handler.Start() diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 8c5da605aa..5995cd1c3b 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -27,7 +27,7 @@ var ( ) var ( - ErrDuplicatedHashes = errors.New("duplicated hashes") + errDuplicateHashes = errors.New("duplicated hashes") ErrAlreadyHasForcedChanges = errors.New("already has a forced change") ErrUnfinalizedAncestor = errors.New("ancestor with changes not applied") ErrNoChanges = errors.New("cannot get the next authority change block number") @@ -50,26 +50,39 @@ func (p *pendingChange) effectiveNumber() uint { return p.announcingHeader.Number + uint(p.delay) } +// appliedBefore compares the effective number between two pending changes +// and returns true if: +// - the current pending change is applied before the target +// - the target is nil +func (p *pendingChange) appliedBefore(target *pendingChange) bool { + if p != nil && target != nil { + return p.effectiveNumber() < target.effectiveNumber() + } + + return p != nil +} + type isDescendantOfFunc func(parent, child common.Hash) (bool, error) type pendingChangeNode struct { - header *types.Header change *pendingChange nodes []*pendingChangeNode } -func (c *pendingChangeNode) importScheduledChange(header *types.Header, pendingChange *pendingChange, +func (c *pendingChangeNode) importScheduledChange(blockHash common.Hash, blockNumber uint, pendingChange *pendingChange, isDescendantOf isDescendantOfFunc) (imported bool, err error) { - if c.header.Hash() == header.Hash() { - return false, errors.New("duplicate block hash while importing change") + announcingHash := c.change.announcingHeader.Hash() + + if blockHash.Equal(announcingHash) { + return false, errDuplicateHashes } - if header.Number <= c.header.Number { + if blockNumber <= c.change.announcingHeader.Number { return false, nil } for _, childrenNodes := range c.nodes { - imported, err := childrenNodes.importScheduledChange(header, pendingChange, isDescendantOf) + imported, err := childrenNodes.importScheduledChange(blockHash, blockNumber, pendingChange, isDescendantOf) if err != nil { return false, fmt.Errorf("could not import change: %w", err) } @@ -79,7 +92,7 @@ func (c *pendingChangeNode) importScheduledChange(header *types.Header, pendingC } } - isDescendant, err := isDescendantOf(c.header.Hash(), header.Hash()) + isDescendant, err := isDescendantOf(announcingHash, blockHash) if err != nil { return false, fmt.Errorf("cannot define ancestry: %w", err) } @@ -88,7 +101,7 @@ func (c *pendingChangeNode) importScheduledChange(header *types.Header, pendingC return false, nil } - pendingChangeNode := &pendingChangeNode{header: header, change: pendingChange, nodes: []*pendingChangeNode{}} + pendingChangeNode := &pendingChangeNode{change: pendingChange, nodes: []*pendingChangeNode{}} c.nodes = append(c.nodes, pendingChangeNode) return true, nil } @@ -98,7 +111,7 @@ type orderedPendingChanges []*pendingChange func (o orderedPendingChanges) Len() int { return len(o) } func (o orderedPendingChanges) Swap(i, j int) { o[i], o[j] = o[j], o[i] } -// Less order by first effective number then by block number +// Less order by effective number and then by block number func (o orderedPendingChanges) Less(i, j int) bool { return o[i].effectiveNumber() < o[j].effectiveNumber() && o[i].announcingHeader.Number < o[j].announcingHeader.Number @@ -175,8 +188,8 @@ func (s *GrandpaState) addForcedChange(header *types.Header, fc types.GrandpaFor for _, change := range s.forcedChanges { changeBlockHash := change.announcingHeader.Hash() - if changeBlockHash == headerHash { - return ErrDuplicatedHashes + if changeBlockHash.Equal(headerHash) { + return errDuplicateHashes } isDescendant, err := s.blockState.IsDescendantOf(changeBlockHash, headerHash) @@ -219,12 +232,12 @@ func (s *GrandpaState) addScheduledChange(header *types.Header, sc types.Grandpa delay: sc.Delay, } - return s.importScheduledChange(header, pendingChange) + return s.importScheduledChange(pendingChange) } -func (s *GrandpaState) importScheduledChange(header *types.Header, pendingChange *pendingChange) error { +func (s *GrandpaState) importScheduledChange(pendingChange *pendingChange) error { defer func() { - logger.Debugf("there are now %d possible standard changes (roots)", len(s.scheduledChangeRoots)) + logger.Debugf("there are now %d possible scheduled changes (roots)", len(s.scheduledChangeRoots)) }() highestFinalizedHeader, err := s.blockState.GetHighestFinalisedHeader() @@ -232,24 +245,26 @@ func (s *GrandpaState) importScheduledChange(header *types.Header, pendingChange return fmt.Errorf("cannot get highest finalized header: %w", err) } - if header.Number <= highestFinalizedHeader.Number { + if pendingChange.announcingHeader.Number <= highestFinalizedHeader.Number { return errors.New("cannot import changes from blocks older then our highest finalized block") } for _, root := range s.scheduledChangeRoots { - imported, err := root.importScheduledChange(header, pendingChange, s.blockState.IsDescendantOf) + imported, err := root.importScheduledChange(pendingChange.announcingHeader.Hash(), + pendingChange.announcingHeader.Number, pendingChange, s.blockState.IsDescendantOf) if err != nil { return fmt.Errorf("could not import change: %w", err) } if imported { - logger.Debugf("changes on header %s (%d) imported succesfully", header.Hash(), header.Number) + logger.Debugf("changes on header %s (%d) imported succesfully", + pendingChange.announcingHeader.Hash(), pendingChange.announcingHeader.Number) return nil } } - pendingChangeNode := &pendingChangeNode{header: header, change: pendingChange, nodes: []*pendingChangeNode{}} + pendingChangeNode := &pendingChangeNode{change: pendingChange, nodes: []*pendingChangeNode{}} s.scheduledChangeRoots = append(s.scheduledChangeRoots, pendingChangeNode) return nil } @@ -278,7 +293,7 @@ func (s *GrandpaState) getApplicableChange(finalizedHash common.Hash, finalizedN continue } - rootHash := root.header.Hash() + rootHash := root.change.announcingHeader.Hash() // if the current root doesn't have the same hash // neither the finalized header is descendant of the root then skip to the next root @@ -296,12 +311,12 @@ func (s *GrandpaState) getApplicableChange(finalizedHash common.Hash, finalizedN // as the changes needs to be applied in order we need to check if our finalized // header is in front of any children, if it is that means some previous change was not applied for _, node := range root.nodes { - isDescendant, err := s.blockState.IsDescendantOf(node.header.Hash(), finalizedHash) + isDescendant, err := s.blockState.IsDescendantOf(node.change.announcingHeader.Hash(), finalizedHash) if err != nil { return nil, fmt.Errorf("cannot verify ancestry: %w", err) } - if node.header.Number <= finalizedNumber && isDescendant { + if node.change.announcingHeader.Number <= finalizedNumber && isDescendant { return nil, ErrUnfinalizedAncestor } } @@ -484,15 +499,15 @@ func (s *GrandpaState) ApplyForcedChanges(importedBlockHeader *types.Header) err return nil } -// forcedChangeOnChain walk through the forced change slice looking for -// a forced change that belong to the same branch as bestBlockHash parameter -func (s *GrandpaState) forcedChangeOnChain(bestBlockHash common.Hash) (change *pendingChange, err error) { +// forcedChangeOnChainOf walk through the forced change slice looking for +// a forced change that belong to the same branch as blockHash parameter +func (s *GrandpaState) forcedChangeOnChainOf(blockHash common.Hash) (*pendingChange, error) { for _, forcedChange := range s.forcedChanges { forcedChangeHeader := forcedChange.announcingHeader var isDescendant bool - isDescendant, err = s.blockState.IsDescendantOf( - forcedChangeHeader.Hash(), bestBlockHash) + isDescendant, err := s.blockState.IsDescendantOf( + forcedChangeHeader.Hash(), blockHash) if err != nil { return nil, fmt.Errorf("cannot verify ancestry: %w", err) @@ -509,12 +524,11 @@ func (s *GrandpaState) forcedChangeOnChain(bestBlockHash common.Hash) (change *p } // scheduledChangeOnChainOf walk only through the scheduled changes roots slice looking for -// a scheduled change that belong to the same branch as bestBlockHash parameter -func (s *GrandpaState) scheduledChangeOnChainOf(bestBlockHash common.Hash) (change *pendingChange, err error) { +// a scheduled change that belongs to the same branch as blockHash parameter +func (s *GrandpaState) scheduledChangeOnChainOf(blockHash common.Hash) (*pendingChange, error) { for _, scheduledChange := range s.scheduledChangeRoots { - var isDescendant bool - isDescendant, err = s.blockState.IsDescendantOf( - scheduledChange.header.Hash(), bestBlockHash) + isDescendant, err := s.blockState.IsDescendantOf( + scheduledChange.change.announcingHeader.Hash(), blockHash) if err != nil { return nil, fmt.Errorf("cannot verify ancestry: %w", err) @@ -533,25 +547,25 @@ func (s *GrandpaState) scheduledChangeOnChainOf(bestBlockHash common.Hash) (chan // NextGrandpaAuthorityChange returns the block number of the next upcoming grandpa authorities change. // It returns 0 if no change is scheduled. func (s *GrandpaState) NextGrandpaAuthorityChange(bestBlockHash common.Hash) (blockNumber uint, err error) { - forcedChange, err := s.forcedChangeOnChain(bestBlockHash) + forcedChange, err := s.forcedChangeOnChainOf(bestBlockHash) if err != nil { - return 0, fmt.Errorf("cannot get forced change: %w", err) + return 0, fmt.Errorf("cannot get forced change on chain of %s: %w", + bestBlockHash, err) } scheduledChange, err := s.scheduledChangeOnChainOf(bestBlockHash) if err != nil { - return 0, fmt.Errorf("cannot get scheduled change: %w", err) - } - - if forcedChange == nil && scheduledChange == nil { - return 0, ErrNoChanges + return 0, fmt.Errorf("cannot get scheduled change on chain of %s: %w", + bestBlockHash, err) } - if forcedChange.announcingHeader.Number < scheduledChange.announcingHeader.Number { + if forcedChange.appliedBefore(scheduledChange) { return forcedChange.effectiveNumber(), nil + } else if scheduledChange != nil { + return scheduledChange.effectiveNumber(), nil } - return scheduledChange.effectiveNumber(), nil + return 0, ErrNoChanges } func authoritiesKey(setID uint64) []byte { diff --git a/dot/state/grandpa_test.go b/dot/state/grandpa_test.go index 7a75327f98..db380dbfd4 100644 --- a/dot/state/grandpa_test.go +++ b/dot/state/grandpa_test.go @@ -140,6 +140,22 @@ func testBlockState(t *testing.T, db chaindb.Database) *BlockState { return bs } +func TestAddScheduledChangesKeepTheRightForkTree(t *testing.T) { + keyring, err := keystore.NewSr25519Keyring() + require.NoError(t, err) + + db := NewInMemoryDB(t) + blockState := testBlockState(t, db) + + gs, err := NewGrandpaStateFromGenesis(db, blockState, nil) + + // create chainA and two forks: chainB and chainC + chainA := issueBlocksWithBABEPrimary(t, keyring.KeyAlice, gs.blockState, testGenesisHeader, 10) + chainB := issueBlocksWithBABEPrimary(t, keyring.KeyBob, gs.blockState, chainA[1], 9) + chainC := issueBlocksWithBABEPrimary(t, keyring.KeyCharlie, gs.blockState, chainA[5], 10) + +} + func TestForcedScheduledChangesOrder(t *testing.T) { keyring, err := keystore.NewSr25519Keyring() require.NoError(t, err) @@ -242,7 +258,7 @@ func TestShouldNotAddMoreThanOneForcedChangeInTheSameFork(t *testing.T) { err = gs.addForcedChange(bobHeaders[2], someForcedChange) require.Error(t, err) - require.ErrorIs(t, err, ErrDuplicatedHashes) + require.ErrorIs(t, err, errDuplicateHashes) } func issueBlocksWithBABEPrimary(t *testing.T, kp *sr25519.Keypair, @@ -292,3 +308,129 @@ func issueBlocksWithBABEPrimary(t *testing.T, kp *sr25519.Keypair, headers = append(headers, issueBlocksWithBABEPrimary(t, kp, bs, header, size-1)...) return headers } + +func TestNextGrandpaAuthorityChange(t *testing.T) { + keyring, err := keystore.NewSr25519Keyring() + require.NoError(t, err) + + tests := map[string]struct { + forcedChange *types.GrandpaForcedChange + forcedChangeAnnoucingIndex int + + scheduledChange *types.GrandpaScheduledChange + scheduledChangeAnnoucingIndex int + + wantErr error + expectedBlockNumber uint + }{ + "no_forced_change_no_scheduled_change": { + wantErr: ErrNoChanges, + }, + "only_forced_change": { + forcedChangeAnnoucingIndex: 2, // in the chain headers slice the index 2 == block number 3 + forcedChange: &types.GrandpaForcedChange{ + Delay: 2, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyAlice.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }, + expectedBlockNumber: 5, + }, + "only_scheduled_change": { + scheduledChangeAnnoucingIndex: 3, // in the chain headers slice the index 3 == block number 4 + scheduledChange: &types.GrandpaScheduledChange{ + Delay: 4, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyAlice.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }, + expectedBlockNumber: 8, + }, + "forced_change_before_scheduled_change": { + forcedChangeAnnoucingIndex: 2, // in the chain headers slice the index 2 == block number 3 + forcedChange: &types.GrandpaForcedChange{ + Delay: 2, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyAlice.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }, + scheduledChangeAnnoucingIndex: 3, // in the chain headers slice the index 3 == block number 4 + scheduledChange: &types.GrandpaScheduledChange{ + Delay: 4, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyAlice.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }, + expectedBlockNumber: 5, // forced change occurs before the scheduled change + }, + "scheduled_change_before_forced_change": { + scheduledChangeAnnoucingIndex: 3, // in the chain headers slice the index 3 == block number 4 + scheduledChange: &types.GrandpaScheduledChange{ + Delay: 4, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyAlice.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }, + forcedChangeAnnoucingIndex: 8, // in the chain headers slice the index 8 == block number 9 + forcedChange: &types.GrandpaForcedChange{ + Delay: 1, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyAlice.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }, + expectedBlockNumber: 8, // scheduled change occurs before the forced change + }, + } + + for tname, tt := range tests { + tt := tt + t.Run(tname, func(t *testing.T) { + t.Parallel() + + db := NewInMemoryDB(t) + blockState := testBlockState(t, db) + + gs, err := NewGrandpaStateFromGenesis(db, blockState, nil) + require.NoError(t, err) + + const sizeOfChain = 10 + + chainHeaders := issueBlocksWithBABEPrimary(t, keyring.KeyAlice, gs.blockState, + testGenesisHeader, sizeOfChain) + + if tt.forcedChange != nil { + gs.addForcedChange(chainHeaders[tt.forcedChangeAnnoucingIndex], + *tt.forcedChange) + } + + if tt.scheduledChange != nil { + gs.addScheduledChange(chainHeaders[tt.scheduledChangeAnnoucingIndex], + *tt.scheduledChange) + } + + lastBlockOnChain := chainHeaders[sizeOfChain].Hash() + blockNumber, err := gs.NextGrandpaAuthorityChange(lastBlockOnChain) + + if tt.wantErr != nil { + require.Error(t, err) + require.EqualError(t, err, tt.wantErr.Error()) + require.Zero(t, blockNumber) + } else { + require.NoError(t, err) + require.Equal(t, tt.expectedBlockNumber, blockNumber) + } + }) + } +} From cd029088dd647d7c7e14f38b81fbe92b418104a1 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Thu, 28 Apr 2022 10:03:42 -0400 Subject: [PATCH 37/93] chore: add unit tests for add scheduled changes to the fork tree --- dot/state/grandpa.go | 12 ++-- dot/state/grandpa_test.go | 128 +++++++++++++++++++++++++++++++++++++- 2 files changed, 135 insertions(+), 5 deletions(-) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 5995cd1c3b..8b54adeb8b 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -84,7 +84,7 @@ func (c *pendingChangeNode) importScheduledChange(blockHash common.Hash, blockNu for _, childrenNodes := range c.nodes { imported, err := childrenNodes.importScheduledChange(blockHash, blockNumber, pendingChange, isDescendantOf) if err != nil { - return false, fmt.Errorf("could not import change: %w", err) + return false, err } if imported { @@ -246,7 +246,7 @@ func (s *GrandpaState) importScheduledChange(pendingChange *pendingChange) error } if pendingChange.announcingHeader.Number <= highestFinalizedHeader.Number { - return errors.New("cannot import changes from blocks older then our highest finalized block") + return ErrLowerThanBestFinalized } for _, root := range s.scheduledChangeRoots { @@ -254,7 +254,7 @@ func (s *GrandpaState) importScheduledChange(pendingChange *pendingChange) error pendingChange.announcingHeader.Number, pendingChange, s.blockState.IsDescendantOf) if err != nil { - return fmt.Errorf("could not import change: %w", err) + return fmt.Errorf("could not import scheduled change: %w", err) } if imported { @@ -264,7 +264,11 @@ func (s *GrandpaState) importScheduledChange(pendingChange *pendingChange) error } } - pendingChangeNode := &pendingChangeNode{change: pendingChange, nodes: []*pendingChangeNode{}} + pendingChangeNode := &pendingChangeNode{ + change: pendingChange, + nodes: []*pendingChangeNode{}, + } + s.scheduledChangeRoots = append(s.scheduledChangeRoots, pendingChangeNode) return nil } diff --git a/dot/state/grandpa_test.go b/dot/state/grandpa_test.go index db380dbfd4..c677e266c4 100644 --- a/dot/state/grandpa_test.go +++ b/dot/state/grandpa_test.go @@ -4,10 +4,12 @@ package state import ( + "fmt" "testing" "github.com/ChainSafe/chaindb" "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/crypto" "github.com/ChainSafe/gossamer/lib/crypto/ed25519" "github.com/ChainSafe/gossamer/lib/crypto/sr25519" @@ -141,6 +143,8 @@ func testBlockState(t *testing.T, db chaindb.Database) *BlockState { } func TestAddScheduledChangesKeepTheRightForkTree(t *testing.T) { + t.Parallel() + keyring, err := keystore.NewSr25519Keyring() require.NoError(t, err) @@ -149,11 +153,133 @@ func TestAddScheduledChangesKeepTheRightForkTree(t *testing.T) { gs, err := NewGrandpaStateFromGenesis(db, blockState, nil) - // create chainA and two forks: chainB and chainC + /* + * create chainA and two forks: chainB and chainC + * + * / -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11 (B) + * 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11 (A) + * \ -> 7 -> 8 -> 9 -> 10 -> 11 -> 12 -> 13 -> 14 -> 15 -> 16 (C) + */ chainA := issueBlocksWithBABEPrimary(t, keyring.KeyAlice, gs.blockState, testGenesisHeader, 10) chainB := issueBlocksWithBABEPrimary(t, keyring.KeyBob, gs.blockState, chainA[1], 9) chainC := issueBlocksWithBABEPrimary(t, keyring.KeyCharlie, gs.blockState, chainA[5], 10) + scheduledChange := &types.GrandpaScheduledChange{ + Delay: 0, // delay of 0 means the modifications should be applied immediately + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyAlice.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + }, + } + + // headersToAdd enables tracking error while adding expecific entries + // to the scheduled change fork tree, eg. + // - adding duplicate hashes entries: while adding the first entry everything should be ok, + // however when adding the second duplicated entry we should expect the errDuplicateHashes error + type headersToAdd struct { + header *types.Header + wantErr error + } + + tests := map[string]struct { + headersWithScheduledChanges []headersToAdd + expectedRoots int + highestFinalizedHeader *types.Header + }{ + "add_scheduled_changes_only_with_roots": { + headersWithScheduledChanges: []headersToAdd{ + {header: chainA[6]}, + {header: chainB[3]}, + }, + expectedRoots: 2, + }, + "add_scheduled_changes_with_roots_and_children": { + headersWithScheduledChanges: []headersToAdd{ + {header: chainA[6]}, {header: chainA[8]}, + {header: chainB[3]}, {header: chainB[7]}, {header: chainB[9]}, + {header: chainC[8]}, + }, + expectedRoots: 3, + }, + "add_scheduled_change_before_highest_finalized_header": { + headersWithScheduledChanges: []headersToAdd{ + {header: chainA[3], wantErr: ErrLowerThanBestFinalized}, + }, + highestFinalizedHeader: chainA[5], + expectedRoots: 0, + }, + "add_scheduled_changes_with_same_hash": { + headersWithScheduledChanges: []headersToAdd{ + {header: chainA[3]}, + {header: chainA[3], wantErr: fmt.Errorf("could not import scheduled change: %w", + errDuplicateHashes)}, + }, + expectedRoots: 0, + }, + } + + for tname, tt := range tests { + tt := tt + t.Run(tname, func(t *testing.T) { + // clear the scheduledChangeRoots after the test ends + // this does not cause race condition because t.Run without + // t.Parallel() blocks until this function returns + defer func() { + gs.scheduledChangeRoots = gs.scheduledChangeRoots[:0] + }() + + updateHighestFinalizedHeaderOrDefault(t, gs.blockState, tt.highestFinalizedHeader, chainA[0]) + + for _, entry := range tt.headersWithScheduledChanges { + err := gs.addScheduledChange(entry.header, *scheduledChange) + + if entry.wantErr != nil { + require.Error(t, err) + require.EqualError(t, err, entry.wantErr.Error()) + return + } else { + require.NoError(t, err) + } + } + + require.Len(t, gs.scheduledChangeRoots, tt.expectedRoots) + + for _, root := range gs.scheduledChangeRoots { + parentHash := root.change.announcingHeader.Hash() + assertDescendantChildren(t, parentHash, gs.blockState.IsDescendantOf, root.nodes) + } + }) + } +} + +func assertDescendantChildren(t *testing.T, parentHash common.Hash, isDescendantOfFunc isDescendantOfFunc, + scheduledChanges []*pendingChangeNode) { + t.Helper() + + for _, scheduled := range scheduledChanges { + scheduledChangeHash := scheduled.change.announcingHeader.Hash() + isDescendant, err := isDescendantOfFunc(parentHash, scheduledChangeHash) + require.NoError(t, err) + require.Truef(t, isDescendant, "%s is not descendant of %s", scheduledChangeHash, parentHash) + + assertDescendantChildren(t, scheduledChangeHash, isDescendantOfFunc, scheduled.nodes) + } +} + +// updateHighestFinalizedHeaderOrDefault will update the current highest finalized header +// with the value of newHighest, if the newHighest is nil then it will use the def value +func updateHighestFinalizedHeaderOrDefault(t *testing.T, bs *BlockState, newHighest, def *types.Header) { + t.Helper() + + round, setID, err := bs.GetHighestRoundAndSetID() + require.NoError(t, err) + + if newHighest != nil { + bs.db.Put(finalisedHashKey(round, setID), newHighest.Hash().ToBytes()) + } else { + bs.db.Put(finalisedHashKey(round, setID), def.Hash().ToBytes()) + } } func TestForcedScheduledChangesOrder(t *testing.T) { From 4eb6098610fc6ff4e96c99761620d446d39760af Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Thu, 28 Apr 2022 15:58:48 -0400 Subject: [PATCH 38/93] chore: added unit tests to apply_forced_changes --- dot/state/grandpa.go | 55 +++++----- dot/state/grandpa_test.go | 211 +++++++++++++++++++++++++++++++++++++- 2 files changed, 237 insertions(+), 29 deletions(-) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 8b54adeb8b..f022abdfc6 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -27,11 +27,13 @@ var ( ) var ( + errPendingScheduledChanges = errors.New("pending scheduled changes needs to be applied") errDuplicateHashes = errors.New("duplicated hashes") - ErrAlreadyHasForcedChanges = errors.New("already has a forced change") - ErrUnfinalizedAncestor = errors.New("ancestor with changes not applied") - ErrNoChanges = errors.New("cannot get the next authority change block number") - ErrLowerThanBestFinalized = errors.New("current finalized is lower than best finalized header") + errAlreadyHasForcedChanges = errors.New("already has a forced change") + errUnfinalizedAncestor = errors.New("ancestor with changes not applied") + errLowerThanBestFinalized = errors.New("current finalized is lower than best finalized header") + + ErrNoChanges = errors.New("cannot get the next authority change block number") ) type pendingChange struct { @@ -138,19 +140,19 @@ func NewGrandpaStateFromGenesis(db chaindb.Database, bs *BlockState, } if err := s.setCurrentSetID(genesisSetID); err != nil { - return nil, err + return nil, fmt.Errorf("cannot set current set id: %w", err) } if err := s.SetLatestRound(0); err != nil { - return nil, err + return nil, fmt.Errorf("cannot set latest round: %w", err) } if err := s.setAuthorities(genesisSetID, genesisAuthorities); err != nil { - return nil, err + return nil, fmt.Errorf("cannot set authorities: %w", err) } if err := s.setChangeSetIDAtBlock(genesisSetID, 0); err != nil { - return nil, err + return nil, fmt.Errorf("cannot set change set id at block 0: %w", err) } return s, nil @@ -198,7 +200,7 @@ func (s *GrandpaState) addForcedChange(header *types.Header, fc types.GrandpaFor } if isDescendant { - return ErrAlreadyHasForcedChanges + return errAlreadyHasForcedChanges } } @@ -246,7 +248,7 @@ func (s *GrandpaState) importScheduledChange(pendingChange *pendingChange) error } if pendingChange.announcingHeader.Number <= highestFinalizedHeader.Number { - return ErrLowerThanBestFinalized + return errLowerThanBestFinalized } for _, root := range s.scheduledChangeRoots { @@ -273,18 +275,19 @@ func (s *GrandpaState) importScheduledChange(pendingChange *pendingChange) error return nil } -// getApplicableChange iterates throught the scheduled change tree roots looking for the change node, which +// getApplicableScheduledChange iterates throught the scheduled change tree roots looking for the change node, which // contains a lower or equal effective number, to apply. When we found that node we update the scheduled change tree roots // with its children that belongs to the same finalized node branch. If we don't find such node we update the scheduled // change tree roots with the change nodes that belongs to the same finalized node branch -func (s *GrandpaState) getApplicableChange(finalizedHash common.Hash, finalizedNumber uint) (change *pendingChange, err error) { +func (s *GrandpaState) getApplicableScheduledChange(finalizedHash common.Hash, finalizedNumber uint) ( + change *pendingChange, err error) { bestFinalizedHeader, err := s.blockState.GetHighestFinalisedHeader() if err != nil { return nil, fmt.Errorf("cannot get highest finalised header: %w", err) } if finalizedNumber < bestFinalizedHeader.Number { - return nil, ErrLowerThanBestFinalized + return nil, errLowerThanBestFinalized } effectiveBlockLowerOrEqualFinalized := func(change *pendingChange) bool { @@ -321,7 +324,7 @@ func (s *GrandpaState) getApplicableChange(finalizedHash common.Hash, finalizedN } if node.change.announcingHeader.Number <= finalizedNumber && isDescendant { - return nil, ErrUnfinalizedAncestor + return nil, errUnfinalizedAncestor } } @@ -378,7 +381,7 @@ func (s *GrandpaState) ApplyScheduledChanges(finalizedHeader *types.Header) erro return nil } - changeToApply, err := s.getApplicableChange(finalizedHash, finalizedHeader.Number) + changeToApply, err := s.getApplicableScheduledChange(finalizedHash, finalizedHeader.Number) if err != nil { return fmt.Errorf("cannot finalize scheduled change: %w", err) } @@ -414,19 +417,19 @@ func (s *GrandpaState) ApplyScheduledChanges(finalizedHeader *types.Header) erro // ApplyForcedChanges will check for if there is a scheduled forced change relative to the // imported block and then apply it otherwise nothing happens func (s *GrandpaState) ApplyForcedChanges(importedBlockHeader *types.Header) error { - importedHash := importedBlockHeader.Hash() + importedBlockHash := importedBlockHeader.Hash() var forcedChange *pendingChange for _, forced := range s.forcedChanges { - announcingHash := forcedChange.announcingHeader.Hash() - effectiveNumber := forcedChange.effectiveNumber() + announcingHash := forced.announcingHeader.Hash() + effectiveNumber := forced.effectiveNumber() - if importedHash.Equal(announcingHash) && effectiveNumber == 0 { + if importedBlockHash.Equal(announcingHash) && effectiveNumber == 0 { forcedChange = forced break } - isDescendant, err := s.blockState.IsDescendantOf(announcingHash, importedHash) + isDescendant, err := s.blockState.IsDescendantOf(announcingHash, importedBlockHash) if err != nil { return fmt.Errorf("cannot check ancestry: %w", err) } @@ -446,25 +449,23 @@ func (s *GrandpaState) ApplyForcedChanges(importedBlockHeader *types.Header) err } forcedChangeHash := forcedChange.announcingHeader.Hash() + bestFinalizedNumber := forcedChange.bestFinalizedNumber // checking for dependant pending scheduled changes for _, scheduled := range s.scheduledChangeRoots { - bestFinalizedNumber := forcedChange.bestFinalizedNumber + if scheduled.change.effectiveNumber() > uint(bestFinalizedNumber) { continue } - scheduledHash := scheduled.change.announcingHeader.Hash() - isDescendant, err := s.blockState.IsDescendantOf(scheduledHash, forcedChangeHash) + scheduledBlockHash := scheduled.change.announcingHeader.Hash() + isDescendant, err := s.blockState.IsDescendantOf(scheduledBlockHash, forcedChangeHash) if err != nil { return fmt.Errorf("cannot check ancestry: %w", err) } if isDescendant { - return fmt.Errorf( - "forced authority fails at block #%d due to pending standard change at block #%d", - forcedChange.announcingHeader.Number, - scheduled.change.effectiveNumber()) + return errPendingScheduledChanges } } diff --git a/dot/state/grandpa_test.go b/dot/state/grandpa_test.go index c677e266c4..e486d348d7 100644 --- a/dot/state/grandpa_test.go +++ b/dot/state/grandpa_test.go @@ -204,7 +204,7 @@ func TestAddScheduledChangesKeepTheRightForkTree(t *testing.T) { }, "add_scheduled_change_before_highest_finalized_header": { headersWithScheduledChanges: []headersToAdd{ - {header: chainA[3], wantErr: ErrLowerThanBestFinalized}, + {header: chainA[3], wantErr: errLowerThanBestFinalized}, }, highestFinalizedHeader: chainA[5], expectedRoots: 0, @@ -283,6 +283,8 @@ func updateHighestFinalizedHeaderOrDefault(t *testing.T, bs *BlockState, newHigh } func TestForcedScheduledChangesOrder(t *testing.T) { + t.Parallel() + keyring, err := keystore.NewSr25519Keyring() require.NoError(t, err) @@ -346,6 +348,8 @@ func TestForcedScheduledChangesOrder(t *testing.T) { } func TestShouldNotAddMoreThanOneForcedChangeInTheSameFork(t *testing.T) { + t.Parallel() + keyring, err := keystore.NewSr25519Keyring() require.NoError(t, err) @@ -376,7 +380,7 @@ func TestShouldNotAddMoreThanOneForcedChangeInTheSameFork(t *testing.T) { err = gs.addForcedChange(aliceHeaders[4], someForcedChange) require.Error(t, err) - require.ErrorIs(t, err, ErrAlreadyHasForcedChanges) + require.ErrorIs(t, err, errAlreadyHasForcedChanges) // adding the same forced change twice err = gs.addForcedChange(bobHeaders[2], someForcedChange) @@ -436,6 +440,8 @@ func issueBlocksWithBABEPrimary(t *testing.T, kp *sr25519.Keypair, } func TestNextGrandpaAuthorityChange(t *testing.T) { + t.Parallel() + keyring, err := keystore.NewSr25519Keyring() require.NoError(t, err) @@ -560,3 +566,204 @@ func TestNextGrandpaAuthorityChange(t *testing.T) { }) } } + +func TestApplyForcedChanges(t *testing.T) { + t.Parallel() + + keyring, err := keystore.NewSr25519Keyring() + require.NoError(t, err) + + db := NewInMemoryDB(t) + blockState := testBlockState(t, db) + + initGrandpaState, err := NewGrandpaStateFromGenesis(db, blockState, nil) + require.NoError(t, err) + + genesisGrandpaVoters := []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyAlice.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + } + + const sizeOfChain = 10 + + // gets the reference of the headers to setup the tests + chainA := issueBlocksWithBABEPrimary(t, keyring.KeyAlice, initGrandpaState.blockState, testGenesisHeader, sizeOfChain) + chainB := issueBlocksWithBABEPrimary(t, keyring.KeyBob, initGrandpaState.blockState, chainA[1], sizeOfChain) + chainC := issueBlocksWithBABEPrimary(t, keyring.KeyCharlie, initGrandpaState.blockState, chainA[5], sizeOfChain) + + tests := map[string]struct { + wantErr error + forcedChanges map[*types.Header]types.GrandpaForcedChange + scheduledChanges map[*types.Header]types.GrandpaScheduledChange + importedHeader *types.Header + expectedGRANDPAAuthoritySet []types.GrandpaAuthoritiesRaw + expectedSetID uint64 + }{ + "no_forced_changes": { + importedHeader: chainA[3], + expectedSetID: 0, + expectedGRANDPAAuthoritySet: genesisGrandpaVoters, + }, + "apply_forced_change_without_pending_scheduled_changes": { + forcedChanges: map[*types.Header]types.GrandpaForcedChange{ + // forced change discovered on block 8 with a delay of 2 blocks + // we should apply the forced change on block 10 + chainA[7]: { + Delay: 2, + BestFinalizedBlock: 3, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyDave.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }, + + // forced change discovered on block 15 from chain C with a delay of 5 blocks + // we should apply the forced change on block 16 + chainC[8]: { + Delay: 1, + BestFinalizedBlock: 3, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyDave.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }, + }, + importedHeader: chainA[9], // import block number 8, + expectedSetID: 1, + expectedGRANDPAAuthoritySet: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyDave.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }, + "import_block_before_change_should_do_nothing": { + forcedChanges: map[*types.Header]types.GrandpaForcedChange{ + // forced change discovered on block 3 with a delay of 5 blocks + // we should apply the forced change on block 8 + chainC[2]: { + Delay: 3, + BestFinalizedBlock: 3, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyDave.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }, + }, + importedHeader: chainC[1], // import block number 7, + expectedSetID: 0, + expectedGRANDPAAuthoritySet: genesisGrandpaVoters, + }, + "import_block_from_other_fork_should_do_nothing": { + forcedChanges: map[*types.Header]types.GrandpaForcedChange{ + // forced change discovered on block 9 from chain C with a delay of 3 blocks + // we should apply the forced change on block 12 + chainC[2]: { + Delay: 3, + BestFinalizedBlock: 3, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyDave.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }, + }, + importedHeader: chainB[9], // import block number 12 from chain B + expectedSetID: 0, + expectedGRANDPAAuthoritySet: genesisGrandpaVoters, + }, + "apply_forced_change_with_pending_scheduled_changes_should_fail": { + scheduledChanges: map[*types.Header]types.GrandpaScheduledChange{ + chainB[3]: { + Delay: 0, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyDave.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }, + }, + forcedChanges: map[*types.Header]types.GrandpaForcedChange{ + // forced change discovered on block 9 from chain C with a delay of 3 blocks + // we should apply the forced change on block 12 + chainC[2]: { + Delay: 3, + BestFinalizedBlock: 3, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyDave.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }, + + // forced change discovered on block 9 from chain B with a delay of 2 blocks + // we should apply the forced change on block 11 + chainB[6]: { + Delay: 2, + BestFinalizedBlock: 6, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyIan.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyEve.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }, + }, + importedHeader: chainB[8], // block number 11 imported + wantErr: errPendingScheduledChanges, + expectedGRANDPAAuthoritySet: genesisGrandpaVoters, + expectedSetID: 0, + }, + } + + for tname, tt := range tests { + tt := tt + + t.Run(tname, func(t *testing.T) { + auths, err := types.GrandpaAuthoritiesRawToAuthorities(genesisGrandpaVoters) + require.NoError(t, err) + + voters := types.NewGrandpaVotersFromAuthorities(auths) + gs, err := NewGrandpaStateFromGenesis(db, blockState, voters) + require.NoError(t, err) + + /* + * create chainA and two forks: chainB and chainC + * + * / -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11 -> 12 (B) + * 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11 (A) + * \ -> 7 -> 8 -> 9 -> 10 -> 11 -> 12 -> 13 -> 14 -> 15 -> 16 (C) + */ + issueBlocksWithBABEPrimary(t, keyring.KeyAlice, gs.blockState, testGenesisHeader, sizeOfChain) + issueBlocksWithBABEPrimary(t, keyring.KeyBob, gs.blockState, chainA[1], sizeOfChain) + issueBlocksWithBABEPrimary(t, keyring.KeyCharlie, gs.blockState, chainA[5], sizeOfChain) + + for header, change := range tt.scheduledChanges { + err := gs.addScheduledChange(header, change) + require.NoError(t, err) + } + + for header, change := range tt.forcedChanges { + err := gs.addForcedChange(header, change) + require.NoError(t, err) + } + + err = gs.ApplyForcedChanges(tt.importedHeader) + if tt.wantErr != nil { + require.Error(t, err) + require.EqualError(t, err, tt.wantErr.Error()) + } else { + require.NoError(t, err) + } + + expectedAuths, err := types.GrandpaAuthoritiesRawToAuthorities(tt.expectedGRANDPAAuthoritySet) + require.NoError(t, err) + expectedVoters := types.NewGrandpaVotersFromAuthorities(expectedAuths) + + gotVoters, err := gs.GetAuthorities(tt.expectedSetID) + require.NoError(t, err) + + require.Equal(t, expectedVoters, gotVoters) + }) + } +} From 959ec9605addfebeabd0a4f141cc65cf89eca4e2 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Wed, 4 May 2022 09:35:07 -0400 Subject: [PATCH 39/93] chore: add tests to `getApplicableChange` --- dot/state/grandpa.go | 129 +++++----- dot/state/grandpa_test.go | 510 ++++++++++++++++++++++++++++++++------ 2 files changed, 505 insertions(+), 134 deletions(-) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index f022abdfc6..9159044c71 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -55,7 +55,7 @@ func (p *pendingChange) effectiveNumber() uint { // appliedBefore compares the effective number between two pending changes // and returns true if: // - the current pending change is applied before the target -// - the target is nil +// - the target is nil and the current contains a value func (p *pendingChange) appliedBefore(target *pendingChange) bool { if p != nil && target != nil { return p.effectiveNumber() < target.effectiveNumber() @@ -275,37 +275,25 @@ func (s *GrandpaState) importScheduledChange(pendingChange *pendingChange) error return nil } -// getApplicableScheduledChange iterates throught the scheduled change tree roots looking for the change node, which +// getApplicableScheduledChange iterates throught the scheduled change tree roots looking for the change node which // contains a lower or equal effective number, to apply. When we found that node we update the scheduled change tree roots // with its children that belongs to the same finalized node branch. If we don't find such node we update the scheduled // change tree roots with the change nodes that belongs to the same finalized node branch func (s *GrandpaState) getApplicableScheduledChange(finalizedHash common.Hash, finalizedNumber uint) ( change *pendingChange, err error) { - bestFinalizedHeader, err := s.blockState.GetHighestFinalisedHeader() - if err != nil { - return nil, fmt.Errorf("cannot get highest finalised header: %w", err) - } - - if finalizedNumber < bestFinalizedHeader.Number { - return nil, errLowerThanBestFinalized - } - - effectiveBlockLowerOrEqualFinalized := func(change *pendingChange) bool { - return change.effectiveNumber() <= finalizedNumber - } - position := -1 - for idx, root := range s.scheduledChangeRoots { - if !effectiveBlockLowerOrEqualFinalized(root.change) { + var changeNode *pendingChangeNode + for _, root := range s.scheduledChangeRoots { + if root.change.effectiveNumber() > finalizedNumber { continue } - rootHash := root.change.announcingHeader.Hash() + changeNodeHash := root.change.announcingHeader.Hash() - // if the current root doesn't have the same hash - // neither the finalized header is descendant of the root then skip to the next root - if !finalizedHash.Equal(rootHash) { - isDescendant, err := s.blockState.IsDescendantOf(rootHash, finalizedHash) + // if the change doesn't have the same hash + // neither the finalized header is descendant of the change then skip to the next root + if !finalizedHash.Equal(changeNodeHash) { + isDescendant, err := s.blockState.IsDescendantOf(changeNodeHash, finalizedHash) if err != nil { return nil, fmt.Errorf("cannot verify ancestry: %w", err) } @@ -315,34 +303,39 @@ func (s *GrandpaState) getApplicableScheduledChange(finalizedHash common.Hash, f } } - // as the changes needs to be applied in order we need to check if our finalized - // header is in front of any children, if it is that means some previous change was not applied - for _, node := range root.nodes { - isDescendant, err := s.blockState.IsDescendantOf(node.change.announcingHeader.Hash(), finalizedHash) + // the changes must be applied in order, so we need to check if our finalized header + // is ahead of any children, if it is that means some previous change was not applied + for _, child := range root.nodes { + isDescendant, err := s.blockState.IsDescendantOf(child.change.announcingHeader.Hash(), finalizedHash) if err != nil { return nil, fmt.Errorf("cannot verify ancestry: %w", err) } - if node.change.announcingHeader.Number <= finalizedNumber && isDescendant { + if child.change.announcingHeader.Number <= finalizedNumber && isDescendant { return nil, errUnfinalizedAncestor } } - position = idx + changeNode = root break } - var changeToApply *pendingChange = nil - - if position > -1 { - pendingChangeNodeAtPosition := s.scheduledChangeRoots[position] - changeToApply = pendingChangeNodeAtPosition.change - - s.scheduledChangeRoots = make([]*pendingChangeNode, len(pendingChangeNodeAtPosition.nodes)) - copy(s.scheduledChangeRoots, pendingChangeNodeAtPosition.nodes) + // if there is no change to be applied then we should keep only + // the scheduled changes which belongs to the finalized header + // otherwise we should update the scheduled roots to be the child + // nodes of the applied scheduled change + if changeNode == nil { + err := s.keepDescendantScheduledChanges(finalizedHash) + if err != nil { + return nil, fmt.Errorf("cannot keep descendant scheduled nodes: %w", err) + } + } else { + change = changeNode.change + s.scheduledChangeRoots = make([]*pendingChangeNode, len(changeNode.nodes)) + copy(s.scheduledChangeRoots, changeNode.nodes) } - return changeToApply, nil + return change, nil } // keepDescendantForcedChanges should keep the forced changes for later blocks that @@ -353,7 +346,7 @@ func (s *GrandpaState) keepDescendantForcedChanges(finalizedHash common.Hash, fi for _, forcedChange := range s.forcedChanges { isDescendant, err := s.blockState.IsDescendantOf(finalizedHash, forcedChange.announcingHeader.Hash()) if err != nil { - return fmt.Errorf("cannot verify ancestry while ancestor: %w", err) + return fmt.Errorf("cannot verify ancestry: %w", err) } if forcedChange.effectiveNumber() > finalizedNumber && isDescendant { @@ -367,6 +360,28 @@ func (s *GrandpaState) keepDescendantForcedChanges(finalizedHash common.Hash, fi return nil } +// keepDescendantScheduledChanges keeps the not applied scheduled changes and remove purged scheduled changes +func (s *GrandpaState) keepDescendantScheduledChanges(finalizedHash common.Hash) error { + onBranchScheduledChanges := []*pendingChangeNode{} + + for _, scheduledChange := range s.scheduledChangeRoots { + scheduledChangeHash := scheduledChange.change.announcingHeader.Hash() + + isDescendant, err := s.blockState.IsDescendantOf(finalizedHash, scheduledChangeHash) + if err != nil { + return fmt.Errorf("cannot verify ancestry: %w", err) + } + + if isDescendant { + onBranchScheduledChanges = append(onBranchScheduledChanges, scheduledChange) + } + } + + s.scheduledChangeRoots = make([]*pendingChangeNode, len(onBranchScheduledChanges)) + copy(s.scheduledChangeRoots, onBranchScheduledChanges) + return nil +} + // ApplyScheduledChange will check the schedules changes in order to find a root // that is equals or behind the finalized number and will apply its authority set changes func (s *GrandpaState) ApplyScheduledChanges(finalizedHeader *types.Header) error { @@ -387,30 +402,30 @@ func (s *GrandpaState) ApplyScheduledChanges(finalizedHeader *types.Header) erro } logger.Debugf("scheduled changes: change to apply: %s", changeToApply) + if changeToApply == nil { + return nil + } - if changeToApply != nil { - newSetID, err := s.IncrementSetID() - if err != nil { - return fmt.Errorf("cannot increment set id: %w", err) - } - - grandpaVotersAuthorities := types.NewGrandpaVotersFromAuthorities(changeToApply.nextAuthorities) - err = s.setAuthorities(newSetID, grandpaVotersAuthorities) - if err != nil { - return fmt.Errorf("cannot set authorities: %w", err) - } - - err = s.setChangeSetIDAtBlock(newSetID, changeToApply.effectiveNumber()) - if err != nil { - return fmt.Errorf("cannot set change set id at block") - } + newSetID, err := s.IncrementSetID() + if err != nil { + return fmt.Errorf("cannot increment set id: %w", err) + } - logger.Debugf("Applying authority set change scheduled at block #%d", - changeToApply.announcingHeader.Number) + grandpaVotersAuthorities := types.NewGrandpaVotersFromAuthorities(changeToApply.nextAuthorities) + err = s.setAuthorities(newSetID, grandpaVotersAuthorities) + if err != nil { + return fmt.Errorf("cannot set authorities: %w", err) + } - // TODO: add afg.applying_scheduled_authority_set_change telemetry info here + err = s.setChangeSetIDAtBlock(newSetID, changeToApply.effectiveNumber()) + if err != nil { + return fmt.Errorf("cannot set change set id at block") } + logger.Debugf("Applying authority set change scheduled at block #%d", + changeToApply.announcingHeader.Number) + + // TODO: add afg.applying_scheduled_authority_set_change telemetry info here return nil } diff --git a/dot/state/grandpa_test.go b/dot/state/grandpa_test.go index e486d348d7..a1f5bab820 100644 --- a/dot/state/grandpa_test.go +++ b/dot/state/grandpa_test.go @@ -568,48 +568,57 @@ func TestNextGrandpaAuthorityChange(t *testing.T) { } func TestApplyForcedChanges(t *testing.T) { - t.Parallel() - keyring, err := keystore.NewSr25519Keyring() require.NoError(t, err) - db := NewInMemoryDB(t) - blockState := testBlockState(t, db) - - initGrandpaState, err := NewGrandpaStateFromGenesis(db, blockState, nil) - require.NoError(t, err) - genesisGrandpaVoters := []types.GrandpaAuthoritiesRaw{ {Key: keyring.KeyAlice.Public().(*sr25519.PublicKey).AsBytes()}, {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, } - const sizeOfChain = 10 + genesisAuths, err := types.GrandpaAuthoritiesRawToAuthorities(genesisGrandpaVoters) + require.NoError(t, err) - // gets the reference of the headers to setup the tests - chainA := issueBlocksWithBABEPrimary(t, keyring.KeyAlice, initGrandpaState.blockState, testGenesisHeader, sizeOfChain) - chainB := issueBlocksWithBABEPrimary(t, keyring.KeyBob, initGrandpaState.blockState, chainA[1], sizeOfChain) - chainC := issueBlocksWithBABEPrimary(t, keyring.KeyCharlie, initGrandpaState.blockState, chainA[5], sizeOfChain) + const sizeOfChain = 10 + genericForks := func(t *testing.T, blockState *BlockState) [][]*types.Header { + + /* + * create chainA and two forks: chainB and chainC + * + * / -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11 -> 12 (B) + * 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11 (A) + * \ -> 7 -> 8 -> 9 -> 10 -> 11 -> 12 -> 13 -> 14 -> 15 -> 16 (C) + */ + chainA := issueBlocksWithBABEPrimary(t, keyring.KeyAlice, blockState, testGenesisHeader, sizeOfChain) + chainB := issueBlocksWithBABEPrimary(t, keyring.KeyBob, blockState, chainA[1], sizeOfChain) + chainC := issueBlocksWithBABEPrimary(t, keyring.KeyCharlie, blockState, chainA[5], sizeOfChain) + + return [][]*types.Header{ + chainA, chainB, chainC, + } + } tests := map[string]struct { wantErr error - forcedChanges map[*types.Header]types.GrandpaForcedChange - scheduledChanges map[*types.Header]types.GrandpaScheduledChange - importedHeader *types.Header + importedHeader [2]int // 2 index array where the 0 index describes the fork and the 1 index describes the header expectedGRANDPAAuthoritySet []types.GrandpaAuthoritiesRaw expectedSetID uint64 + + generateForks func(t *testing.T, blockState *BlockState) [][]*types.Header + changes func(*GrandpaState, [][]*types.Header) }{ "no_forced_changes": { - importedHeader: chainA[3], + generateForks: genericForks, + importedHeader: [2]int{0, 3}, // chain A from and header number 4 expectedSetID: 0, expectedGRANDPAAuthoritySet: genesisGrandpaVoters, }, "apply_forced_change_without_pending_scheduled_changes": { - forcedChanges: map[*types.Header]types.GrandpaForcedChange{ - // forced change discovered on block 8 with a delay of 2 blocks - // we should apply the forced change on block 10 - chainA[7]: { + generateForks: genericForks, + changes: func(gs *GrandpaState, headers [][]*types.Header) { + chainABlock8 := headers[0][7] + gs.addForcedChange(chainABlock8, types.GrandpaForcedChange{ Delay: 2, BestFinalizedBlock: 3, Auths: []types.GrandpaAuthoritiesRaw{ @@ -617,11 +626,10 @@ func TestApplyForcedChanges(t *testing.T) { {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, {Key: keyring.KeyDave.Public().(*sr25519.PublicKey).AsBytes()}, }, - }, + }) - // forced change discovered on block 15 from chain C with a delay of 5 blocks - // we should apply the forced change on block 16 - chainC[8]: { + chainCBlock15 := headers[2][8] + gs.addForcedChange(chainCBlock15, types.GrandpaForcedChange{ Delay: 1, BestFinalizedBlock: 3, Auths: []types.GrandpaAuthoritiesRaw{ @@ -629,9 +637,9 @@ func TestApplyForcedChanges(t *testing.T) { {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, {Key: keyring.KeyDave.Public().(*sr25519.PublicKey).AsBytes()}, }, - }, + }) }, - importedHeader: chainA[9], // import block number 8, + importedHeader: [2]int{0, 9}, // import block number 10 from fork A expectedSetID: 1, expectedGRANDPAAuthoritySet: []types.GrandpaAuthoritiesRaw{ {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, @@ -639,11 +647,11 @@ func TestApplyForcedChanges(t *testing.T) { {Key: keyring.KeyDave.Public().(*sr25519.PublicKey).AsBytes()}, }, }, - "import_block_before_change_should_do_nothing": { - forcedChanges: map[*types.Header]types.GrandpaForcedChange{ - // forced change discovered on block 3 with a delay of 5 blocks - // we should apply the forced change on block 8 - chainC[2]: { + "import_block_before_forced_change_should_do_nothing": { + generateForks: genericForks, + changes: func(gs *GrandpaState, headers [][]*types.Header) { + chainCBlock9 := headers[2][2] + gs.addForcedChange(chainCBlock9, types.GrandpaForcedChange{ Delay: 3, BestFinalizedBlock: 3, Auths: []types.GrandpaAuthoritiesRaw{ @@ -651,17 +659,17 @@ func TestApplyForcedChanges(t *testing.T) { {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, {Key: keyring.KeyDave.Public().(*sr25519.PublicKey).AsBytes()}, }, - }, + }) }, - importedHeader: chainC[1], // import block number 7, + importedHeader: [2]int{2, 1}, // import block number 7 from chain C expectedSetID: 0, expectedGRANDPAAuthoritySet: genesisGrandpaVoters, }, - "import_block_from_other_fork_should_do_nothing": { - forcedChanges: map[*types.Header]types.GrandpaForcedChange{ - // forced change discovered on block 9 from chain C with a delay of 3 blocks - // we should apply the forced change on block 12 - chainC[2]: { + "import_block_from_another_fork_should_do_nothing": { + generateForks: genericForks, + changes: func(gs *GrandpaState, headers [][]*types.Header) { + chainCBlock9 := headers[2][2] + gs.addForcedChange(chainCBlock9, types.GrandpaForcedChange{ Delay: 3, BestFinalizedBlock: 3, Auths: []types.GrandpaAuthoritiesRaw{ @@ -669,25 +677,25 @@ func TestApplyForcedChanges(t *testing.T) { {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, {Key: keyring.KeyDave.Public().(*sr25519.PublicKey).AsBytes()}, }, - }, + }) }, - importedHeader: chainB[9], // import block number 12 from chain B + importedHeader: [2]int{1, 9}, // import block number 12 from chain B expectedSetID: 0, expectedGRANDPAAuthoritySet: genesisGrandpaVoters, }, "apply_forced_change_with_pending_scheduled_changes_should_fail": { - scheduledChanges: map[*types.Header]types.GrandpaScheduledChange{ - chainB[3]: { + generateForks: genericForks, + changes: func(gs *GrandpaState, headers [][]*types.Header) { + chainBBlock6 := headers[1][3] + gs.addScheduledChange(chainBBlock6, types.GrandpaScheduledChange{ Delay: 0, Auths: []types.GrandpaAuthoritiesRaw{ {Key: keyring.KeyDave.Public().(*sr25519.PublicKey).AsBytes()}, }, - }, - }, - forcedChanges: map[*types.Header]types.GrandpaForcedChange{ - // forced change discovered on block 9 from chain C with a delay of 3 blocks - // we should apply the forced change on block 12 - chainC[2]: { + }) + + chainCBlock9 := headers[2][2] + gs.addForcedChange(chainCBlock9, types.GrandpaForcedChange{ Delay: 3, BestFinalizedBlock: 3, Auths: []types.GrandpaAuthoritiesRaw{ @@ -695,11 +703,10 @@ func TestApplyForcedChanges(t *testing.T) { {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, {Key: keyring.KeyDave.Public().(*sr25519.PublicKey).AsBytes()}, }, - }, + }) - // forced change discovered on block 9 from chain B with a delay of 2 blocks - // we should apply the forced change on block 11 - chainB[6]: { + chainBBlock9 := headers[1][6] + gs.addForcedChange(chainBBlock9, types.GrandpaForcedChange{ Delay: 2, BestFinalizedBlock: 6, Auths: []types.GrandpaAuthoritiesRaw{ @@ -707,9 +714,9 @@ func TestApplyForcedChanges(t *testing.T) { {Key: keyring.KeyIan.Public().(*sr25519.PublicKey).AsBytes()}, {Key: keyring.KeyEve.Public().(*sr25519.PublicKey).AsBytes()}, }, - }, + }) }, - importedHeader: chainB[8], // block number 11 imported + importedHeader: [2]int{1, 8}, // block number 11 imported wantErr: errPendingScheduledChanges, expectedGRANDPAAuthoritySet: genesisGrandpaVoters, expectedSetID: 0, @@ -720,35 +727,24 @@ func TestApplyForcedChanges(t *testing.T) { tt := tt t.Run(tname, func(t *testing.T) { - auths, err := types.GrandpaAuthoritiesRawToAuthorities(genesisGrandpaVoters) - require.NoError(t, err) + t.Parallel() + + db := NewInMemoryDB(t) + blockState := testBlockState(t, db) - voters := types.NewGrandpaVotersFromAuthorities(auths) + voters := types.NewGrandpaVotersFromAuthorities(genesisAuths) gs, err := NewGrandpaStateFromGenesis(db, blockState, voters) require.NoError(t, err) - /* - * create chainA and two forks: chainB and chainC - * - * / -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11 -> 12 (B) - * 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11 (A) - * \ -> 7 -> 8 -> 9 -> 10 -> 11 -> 12 -> 13 -> 14 -> 15 -> 16 (C) - */ - issueBlocksWithBABEPrimary(t, keyring.KeyAlice, gs.blockState, testGenesisHeader, sizeOfChain) - issueBlocksWithBABEPrimary(t, keyring.KeyBob, gs.blockState, chainA[1], sizeOfChain) - issueBlocksWithBABEPrimary(t, keyring.KeyCharlie, gs.blockState, chainA[5], sizeOfChain) - - for header, change := range tt.scheduledChanges { - err := gs.addScheduledChange(header, change) - require.NoError(t, err) + forks := tt.generateForks(t, blockState) + if tt.changes != nil { + tt.changes(gs, forks) } - for header, change := range tt.forcedChanges { - err := gs.addForcedChange(header, change) - require.NoError(t, err) - } + selectedFork := forks[tt.importedHeader[0]] + selectedImportedHeader := selectedFork[tt.importedHeader[1]] - err = gs.ApplyForcedChanges(tt.importedHeader) + err = gs.ApplyForcedChanges(selectedImportedHeader) if tt.wantErr != nil { require.Error(t, err) require.EqualError(t, err, tt.wantErr.Error()) @@ -756,6 +752,10 @@ func TestApplyForcedChanges(t *testing.T) { require.NoError(t, err) } + currentSetID, err := gs.GetCurrentSetID() + require.NoError(t, err) + require.Equal(t, tt.expectedSetID, currentSetID) + expectedAuths, err := types.GrandpaAuthoritiesRawToAuthorities(tt.expectedGRANDPAAuthoritySet) require.NoError(t, err) expectedVoters := types.NewGrandpaVotersFromAuthorities(expectedAuths) @@ -767,3 +767,359 @@ func TestApplyForcedChanges(t *testing.T) { }) } } + +func TestApplyScheduledChangesKeepDescendantForcedChanges(t *testing.T) { + keyring, err := keystore.NewSr25519Keyring() + require.NoError(t, err) + + genesisGrandpaVoters := []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyAlice.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + } + + genesisAuths, err := types.GrandpaAuthoritiesRawToAuthorities(genesisGrandpaVoters) + require.NoError(t, err) + + const sizeOfChain = 10 + genericForks := func(t *testing.T, blockState *BlockState) [][]*types.Header { + + /* + * create chainA and two forks: chainB and chainC + * + * / -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11 -> 12 (B) + * 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11 (A) + * \ -> 7 -> 8 -> 9 -> 10 -> 11 -> 12 -> 13 -> 14 -> 15 -> 16 (C) + */ + chainA := issueBlocksWithBABEPrimary(t, keyring.KeyAlice, blockState, testGenesisHeader, sizeOfChain) + chainB := issueBlocksWithBABEPrimary(t, keyring.KeyBob, blockState, chainA[1], sizeOfChain) + chainC := issueBlocksWithBABEPrimary(t, keyring.KeyCharlie, blockState, chainA[5], sizeOfChain) + + return [][]*types.Header{ + chainA, chainB, chainC, + } + } + + tests := map[string]struct { + finalizedHeader [2]int // 2 index array where the 0 index describes the fork and the 1 index describes the header + + generateForks func(*testing.T, *BlockState) [][]*types.Header + changes func(*GrandpaState, [][]*types.Header) + + wantErr error + + expectedForcedChangesLen int + }{ + "no_forced_changes": { + generateForks: genericForks, + expectedForcedChangesLen: 0, + }, + "finalized_hash_should_keep_descendant_forced_changes": { + generateForks: genericForks, + expectedForcedChangesLen: 1, + changes: func(gs *GrandpaState, headers [][]*types.Header) { + chainABlock6 := headers[0][5] + gs.addForcedChange(chainABlock6, types.GrandpaForcedChange{ + Delay: 1, + BestFinalizedBlock: 3, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyDave.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }) + + chainBBlock6 := headers[1][3] + gs.addForcedChange(chainBBlock6, types.GrandpaForcedChange{ + Delay: 2, + BestFinalizedBlock: 3, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyDave.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }) + }, + finalizedHeader: [2]int{0, 3}, //finalize header number 4 from chain A + }, + } + + for tname, tt := range tests { + tt := tt + t.Run(tname, func(t *testing.T) { + t.Parallel() + + db := NewInMemoryDB(t) + blockState := testBlockState(t, db) + + voters := types.NewGrandpaVotersFromAuthorities(genesisAuths) + gs, err := NewGrandpaStateFromGenesis(db, blockState, voters) + require.NoError(t, err) + + forks := tt.generateForks(t, gs.blockState) + + if tt.changes != nil { + tt.changes(gs, forks) + } + + selectedFork := forks[tt.finalizedHeader[0]] + selectedFinalizedHeader := selectedFork[tt.finalizedHeader[1]] + + err = gs.keepDescendantForcedChanges(selectedFinalizedHeader.Hash(), selectedFinalizedHeader.Number) + if tt.wantErr != nil { + require.EqualError(t, err, tt.wantErr.Error()) + } else { + require.NoError(t, err) + + require.Len(t, gs.forcedChanges, tt.expectedForcedChangesLen) + + for _, forcedChange := range gs.forcedChanges { + isDescendant, err := gs.blockState.IsDescendantOf( + selectedFinalizedHeader.Hash(), forcedChange.announcingHeader.Hash()) + + require.NoError(t, err) + require.True(t, isDescendant) + } + } + }) + } +} + +func TestApplyScheduledChangeGetApplicableChange(t *testing.T) { + keyring, err := keystore.NewSr25519Keyring() + require.NoError(t, err) + + genesisGrandpaVoters := []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyAlice.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + } + + genesisAuths, err := types.GrandpaAuthoritiesRawToAuthorities(genesisGrandpaVoters) + require.NoError(t, err) + + const sizeOfChain = 10 + genericForks := func(t *testing.T, blockState *BlockState) [][]*types.Header { + /* + * create chainA and two forks: chainB and chainC + * + * / -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11 -> 12 (B) + * 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11 (A) + * \ -> 7 -> 8 -> 9 -> 10 -> 11 -> 12 -> 13 -> 14 -> 15 -> 16 (C) + */ + chainA := issueBlocksWithBABEPrimary(t, keyring.KeyAlice, blockState, testGenesisHeader, sizeOfChain) + chainB := issueBlocksWithBABEPrimary(t, keyring.KeyBob, blockState, chainA[1], sizeOfChain) + chainC := issueBlocksWithBABEPrimary(t, keyring.KeyCharlie, blockState, chainA[5], sizeOfChain) + + return [][]*types.Header{ + chainA, chainB, chainC, + } + } + + tests := map[string]struct { + finalizedHeader [2]int + changes func(*GrandpaState, [][]*types.Header) + generateForks func(*testing.T, *BlockState) [][]*types.Header + wantErr error + expectedChange *pendingChange + expectedScheduledChangeRootsLen int + }{ + "empty_scheduled_changes": { + generateForks: genericForks, + finalizedHeader: [2]int{0, 1}, // finalized block from chainA header number 2 + }, + "scheduled_change_being_finalized_should_be_applied": { + generateForks: genericForks, + changes: func(gs *GrandpaState, headers [][]*types.Header) { + chainABlock6 := headers[0][5] + gs.addScheduledChange(chainABlock6, types.GrandpaScheduledChange{ + Delay: 0, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyIan.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyEve.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }) + }, + expectedChange: &pendingChange{ + delay: 0, + nextAuthorities: func() []types.Authority { + auths, _ := types.GrandpaAuthoritiesRawToAuthorities( + []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyIan.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyEve.Public().(*sr25519.PublicKey).AsBytes()}, + }, + ) + return auths + }(), + }, + finalizedHeader: [2]int{0, 5}, // finalize block number 6 from chain A + }, + "apply_change_and_update_scheduled_changes_with_the_children": { + generateForks: genericForks, + changes: func(gs *GrandpaState, headers [][]*types.Header) { + chainBBlock4 := headers[1][1] // block number 4 from chain B + gs.addScheduledChange(chainBBlock4, types.GrandpaScheduledChange{ + Delay: 0, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyFerdie.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyGeorge.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }) + + chainBBlock7 := headers[1][4] // block number 7 from chain B + gs.addScheduledChange(chainBBlock7, types.GrandpaScheduledChange{ + Delay: 0, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyAlice.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyIan.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }) + + chainCBlock7 := headers[2][0] // block number 7 from chain C + gs.addScheduledChange(chainCBlock7, types.GrandpaScheduledChange{ + Delay: 0, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyIan.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyEve.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }) + }, + finalizedHeader: [2]int{1, 1}, // finalize block number 6 from chain A + expectedScheduledChangeRootsLen: 1, + expectedChange: &pendingChange{ + delay: 0, + nextAuthorities: func() []types.Authority { + auths, _ := types.GrandpaAuthoritiesRawToAuthorities( + []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyFerdie.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyGeorge.Public().(*sr25519.PublicKey).AsBytes()}, + }, + ) + return auths + }(), + }, + }, + "finalized_header_with_no_scheduled_change_should_purge_other_pending_changes": { + generateForks: genericForks, + expectedScheduledChangeRootsLen: 1, + changes: func(gs *GrandpaState, headers [][]*types.Header) { + chainABlock8 := headers[0][7] // block 8 from chain A should keep + gs.addScheduledChange(chainABlock8, types.GrandpaScheduledChange{ + Delay: 0, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyIan.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyEve.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }) + + chainBBlock9 := headers[1][6] // block 9 from chain B should be pruned + gs.addScheduledChange(chainBBlock9, types.GrandpaScheduledChange{ + Delay: 0, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyIan.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyEve.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }) + + chainCBlock8 := headers[2][1] // block 8 from chain C should be pruned + gs.addScheduledChange(chainCBlock8, types.GrandpaScheduledChange{ + Delay: 0, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyIan.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyEve.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }) + }, + finalizedHeader: [2]int{0, 6}, // finalize block number 7 from chain A + }, + "finalizing_header_with_pending_changes_should_return_unfinalized_acestor": { + generateForks: genericForks, + changes: func(gs *GrandpaState, headers [][]*types.Header) { + chainABlock4 := headers[0][3] // block 4 from chain A + gs.addScheduledChange(chainABlock4, types.GrandpaScheduledChange{ + Delay: 0, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyAlice.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyIan.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyEve.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }) + + // change on block 5 from chain A should be a child + // node of scheduled change on block 4 from chain A + chainABlock5 := headers[0][5] + gs.addScheduledChange(chainABlock5, types.GrandpaScheduledChange{ + Delay: 0, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyIan.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyEve.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }) + }, + finalizedHeader: [2]int{0, 6}, // finalize block number 7 from chain A + wantErr: errUnfinalizedAncestor, + }, + } + + for tname, tt := range tests { + tt := tt + t.Run(tname, func(t *testing.T) { + t.Parallel() + + db := NewInMemoryDB(t) + blockState := testBlockState(t, db) + + voters := types.NewGrandpaVotersFromAuthorities(genesisAuths) + gs, err := NewGrandpaStateFromGenesis(db, blockState, voters) + require.NoError(t, err) + + forks := tt.generateForks(t, gs.blockState) + + if tt.changes != nil { + tt.changes(gs, forks) + } + + // saving the current state of scheduled changes to compare + // with the next state in the case of an error (should keep the same) + previousScheduledChanges := make([]*pendingChangeNode, len(gs.scheduledChangeRoots)) + copy(previousScheduledChanges, gs.scheduledChangeRoots) + + selectedChain := forks[tt.finalizedHeader[0]] + selectedHeader := selectedChain[tt.finalizedHeader[1]] + + change, err := gs.getApplicableScheduledChange(selectedHeader.Hash(), selectedHeader.Number) + if tt.wantErr != nil { + require.EqualError(t, err, tt.wantErr.Error()) + require.Equal(t, previousScheduledChanges, gs.scheduledChangeRoots) + return + } + + if tt.expectedChange != nil { + require.NoError(t, err) + require.Equal(t, tt.expectedChange.delay, change.delay) + require.Equal(t, tt.expectedChange.nextAuthorities, change.nextAuthorities) + } else { + require.Nil(t, change) + } + + require.Len(t, gs.scheduledChangeRoots, tt.expectedScheduledChangeRootsLen) + // make sure all the next scheduled changes are descendant of the finalized hash + assertDescendantChildren(t, + selectedHeader.Hash(), gs.blockState.IsDescendantOf, gs.scheduledChangeRoots) + }) + } +} + +func TestApplyScheduledChange(t *testing.T) { + +} From ca477fc9ab7f211b0a02fe2f1f5b37e1e5e9c8ad Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Wed, 4 May 2022 10:44:14 -0400 Subject: [PATCH 40/93] chore: resolve lint --- dot/digest/digest.go | 7 +- dot/state/grandpa.go | 18 +-- dot/state/grandpa_test.go | 245 +++++++++++++++++++++++++++++++++++++- 3 files changed, 255 insertions(+), 15 deletions(-) diff --git a/dot/digest/digest.go b/dot/digest/digest.go index faa7812214..1482161f79 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -160,7 +160,10 @@ func (h *Handler) handleBlockImport(ctx context.Context) { } h.HandleDigests(&block.Header) - h.grandpaState.ApplyForcedChanges(&block.Header) + err := h.grandpaState.ApplyForcedChanges(&block.Header) + if err != nil { + h.logger.Errorf("cannot apply forced changes: %w", err) + } case <-ctx.Done(): return } @@ -187,7 +190,7 @@ func (h *Handler) handleBlockFinalisation(ctx context.Context) { err = h.grandpaState.ApplyScheduledChanges(&info.Header) if err != nil { - h.logger.Errorf("failed to apply standard scheduled changes on block finalization: %w", err) + h.logger.Errorf("failed to apply standard scheduled changes on block finalisation: %w", err) } case <-ctx.Done(): diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 9159044c71..19a2a9f6c1 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -260,7 +260,7 @@ func (s *GrandpaState) importScheduledChange(pendingChange *pendingChange) error } if imported { - logger.Debugf("changes on header %s (%d) imported succesfully", + logger.Debugf("changes on header %s (%d) imported successfully", pendingChange.announcingHeader.Hash(), pendingChange.announcingHeader.Number) return nil } @@ -275,9 +275,10 @@ func (s *GrandpaState) importScheduledChange(pendingChange *pendingChange) error return nil } -// getApplicableScheduledChange iterates throught the scheduled change tree roots looking for the change node which -// contains a lower or equal effective number, to apply. When we found that node we update the scheduled change tree roots -// with its children that belongs to the same finalized node branch. If we don't find such node we update the scheduled +// getApplicableScheduledChange iterates through the scheduled change tree roots looking +// for the change node which contains a lower or equal effective number, to apply. +// When we found that node we update the scheduled change tree roots with its children +// that belongs to the same finalized node branch. If we don't find such node we update the scheduled // change tree roots with the change nodes that belongs to the same finalized node branch func (s *GrandpaState) getApplicableScheduledChange(finalizedHash common.Hash, finalizedNumber uint) ( change *pendingChange, err error) { @@ -382,7 +383,7 @@ func (s *GrandpaState) keepDescendantScheduledChanges(finalizedHash common.Hash) return nil } -// ApplyScheduledChange will check the schedules changes in order to find a root +// ApplyScheduledChanges will check the schedules changes in order to find a root // that is equals or behind the finalized number and will apply its authority set changes func (s *GrandpaState) ApplyScheduledChanges(finalizedHeader *types.Header) error { finalizedHash := finalizedHeader.Hash() @@ -401,11 +402,12 @@ func (s *GrandpaState) ApplyScheduledChanges(finalizedHeader *types.Header) erro return fmt.Errorf("cannot finalize scheduled change: %w", err) } - logger.Debugf("scheduled changes: change to apply: %s", changeToApply) if changeToApply == nil { return nil } + logger.Debugf("scheduled changes: change to apply: %s", changeToApply) + newSetID, err := s.IncrementSetID() if err != nil { return fmt.Errorf("cannot increment set id: %w", err) @@ -540,7 +542,7 @@ func (s *GrandpaState) forcedChangeOnChainOf(blockHash common.Hash) (*pendingCha return forcedChange, nil } - return nil, nil + return nil, nil //nolint:nilnil } // scheduledChangeOnChainOf walk only through the scheduled changes roots slice looking for @@ -561,7 +563,7 @@ func (s *GrandpaState) scheduledChangeOnChainOf(blockHash common.Hash) (*pending return scheduledChange.change, nil } - return nil, nil + return nil, nil //nolint:nilnil } // NextGrandpaAuthorityChange returns the block number of the next upcoming grandpa authorities change. diff --git a/dot/state/grandpa_test.go b/dot/state/grandpa_test.go index a1f5bab820..2bc8901e51 100644 --- a/dot/state/grandpa_test.go +++ b/dot/state/grandpa_test.go @@ -238,9 +238,9 @@ func TestAddScheduledChangesKeepTheRightForkTree(t *testing.T) { require.Error(t, err) require.EqualError(t, err, entry.wantErr.Error()) return - } else { - require.NoError(t, err) } + + require.NoError(t, err) } require.Len(t, gs.scheduledChangeRoots, tt.expectedRoots) @@ -600,8 +600,9 @@ func TestApplyForcedChanges(t *testing.T) { } tests := map[string]struct { - wantErr error - importedHeader [2]int // 2 index array where the 0 index describes the fork and the 1 index describes the header + wantErr error + // 2 index array where the 0 index describes the fork and the 1 index describes the header + importedHeader [2]int expectedGRANDPAAuthoritySet []types.GrandpaAuthoritiesRaw expectedSetID uint64 @@ -1041,7 +1042,7 @@ func TestApplyScheduledChangeGetApplicableChange(t *testing.T) { }, finalizedHeader: [2]int{0, 6}, // finalize block number 7 from chain A }, - "finalizing_header_with_pending_changes_should_return_unfinalized_acestor": { + "finalising_header_with_pending_changes_should_return_unfinalized_acestor": { generateForks: genericForks, changes: func(gs *GrandpaState, headers [][]*types.Header) { chainABlock4 := headers[0][3] // block 4 from chain A @@ -1121,5 +1122,239 @@ func TestApplyScheduledChangeGetApplicableChange(t *testing.T) { } func TestApplyScheduledChange(t *testing.T) { + keyring, err := keystore.NewSr25519Keyring() + require.NoError(t, err) + + genesisGrandpaVoters := []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyAlice.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + } + + genesisAuths, err := types.GrandpaAuthoritiesRawToAuthorities(genesisGrandpaVoters) + require.NoError(t, err) + + const sizeOfChain = 10 + genericForks := func(t *testing.T, blockState *BlockState) [][]*types.Header { + + /* + * create chainA and two forks: chainB and chainC + * + * / -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11 -> 12 (B) + * 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11 (A) + * \ -> 7 -> 8 -> 9 -> 10 -> 11 -> 12 -> 13 -> 14 -> 15 -> 16 (C) + */ + chainA := issueBlocksWithBABEPrimary(t, keyring.KeyAlice, blockState, testGenesisHeader, sizeOfChain) + chainB := issueBlocksWithBABEPrimary(t, keyring.KeyBob, blockState, chainA[1], sizeOfChain) + chainC := issueBlocksWithBABEPrimary(t, keyring.KeyCharlie, blockState, chainA[5], sizeOfChain) + + return [][]*types.Header{ + chainA, chainB, chainC, + } + } + + tests := map[string]struct { + finalizedHeader [2]int // 2 index array where the 0 index describes the fork and the 1 index describes the header + + generateForks func(*testing.T, *BlockState) [][]*types.Header + changes func(*GrandpaState, [][]*types.Header) + + wantErr error + expectedScheduledChangeRootsLen int + expectedForcedChangesLen int + expectedSetID uint64 + expectedAuthoritySet []types.GrandpaVoter + changeSetIDAt uint + }{ + "empty_scheduled_changes_only_update_the_forced_changes": { + generateForks: genericForks, + changes: func(gs *GrandpaState, headers [][]*types.Header) { + chainABlock6 := headers[0][5] // block number 6 from chain A + gs.addForcedChange(chainABlock6, types.GrandpaForcedChange{ + Delay: 1, + BestFinalizedBlock: 3, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyDave.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }) + + chainBBlock6 := headers[1][3] // block number 6 from chain B + gs.addForcedChange(chainBBlock6, types.GrandpaForcedChange{ + Delay: 2, + BestFinalizedBlock: 3, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyDave.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }) + }, + finalizedHeader: [2]int{0, 3}, + expectedForcedChangesLen: 1, + expectedAuthoritySet: func() []types.GrandpaVoter { + auths, _ := types.GrandpaAuthoritiesRawToAuthorities(genesisGrandpaVoters) + return types.NewGrandpaVotersFromAuthorities(auths) + }(), + }, + "pending_scheduled_changes_should_return_error": { + generateForks: genericForks, + changes: func(gs *GrandpaState, headers [][]*types.Header) { + chainABlock4 := headers[0][3] // block 4 from chain A + gs.addScheduledChange(chainABlock4, types.GrandpaScheduledChange{ + Delay: 0, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyAlice.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyIan.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyEve.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }) + + // change on block 5 from chain A should be a child + // node of scheduled change on block 4 from chain A + chainABlock5 := headers[0][5] + err = gs.addScheduledChange(chainABlock5, types.GrandpaScheduledChange{ + Delay: 0, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyIan.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyEve.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }) + }, + finalizedHeader: [2]int{0, 6}, // finalize block number 7 from chain A + wantErr: fmt.Errorf("cannot finalize scheduled change: %w", errUnfinalizedAncestor), + expectedScheduledChangeRootsLen: 1, // expected one root len as the second change is a child + expectedAuthoritySet: func() []types.GrandpaVoter { + auths, _ := types.GrandpaAuthoritiesRawToAuthorities(genesisGrandpaVoters) + return types.NewGrandpaVotersFromAuthorities(auths) + }(), + }, + "no_changes_to_apply_should_only_update_the_scheduled_roots": { + generateForks: genericForks, + changes: func(gs *GrandpaState, headers [][]*types.Header) { + chainBBlock6 := headers[1][3] // block 6 from chain B + gs.addScheduledChange(chainBBlock6, types.GrandpaScheduledChange{ + Delay: 0, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyAlice.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyIan.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyEve.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }) + + chainBBlock8 := headers[1][5] // block number 8 from chain B + err = gs.addScheduledChange(chainBBlock8, types.GrandpaScheduledChange{ + Delay: 0, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyIan.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyEve.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }) + }, + finalizedHeader: [2]int{2, 1}, // finalize block number 8 from chain C + expectedScheduledChangeRootsLen: 0, + expectedAuthoritySet: func() []types.GrandpaVoter { + auths, _ := types.GrandpaAuthoritiesRawToAuthorities(genesisGrandpaVoters) + return types.NewGrandpaVotersFromAuthorities(auths) + }(), + }, + "apply_scheduled_change_should_change_voters_and_set_id": { + generateForks: genericForks, + changes: func(gs *GrandpaState, headers [][]*types.Header) { + chainBBlock6 := headers[1][3] // block 6 from chain B + gs.addScheduledChange(chainBBlock6, types.GrandpaScheduledChange{ + Delay: 0, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyAlice.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyIan.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyEve.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }) + chainBBlock8 := headers[1][5] // block number 8 from chain B + err = gs.addScheduledChange(chainBBlock8, types.GrandpaScheduledChange{ + Delay: 0, + Auths: []types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyIan.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyEve.Public().(*sr25519.PublicKey).AsBytes()}, + }, + }) + }, + finalizedHeader: [2]int{1, 3}, // finalize block number 6 from chain B + // the child (block number 8 from chain B) should be the next scheduled change root + expectedScheduledChangeRootsLen: 1, + expectedSetID: 1, + changeSetIDAt: 6, + expectedAuthoritySet: func() []types.GrandpaVoter { + auths, _ := types.GrandpaAuthoritiesRawToAuthorities([]types.GrandpaAuthoritiesRaw{ + {Key: keyring.KeyAlice.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyIan.Public().(*sr25519.PublicKey).AsBytes()}, + {Key: keyring.KeyEve.Public().(*sr25519.PublicKey).AsBytes()}, + }) + return types.NewGrandpaVotersFromAuthorities(auths) + }(), + }, + } + + for tname, tt := range tests { + tt := tt + t.Run(tname, func(t *testing.T) { + t.Parallel() + + db := NewInMemoryDB(t) + blockState := testBlockState(t, db) + + voters := types.NewGrandpaVotersFromAuthorities(genesisAuths) + gs, err := NewGrandpaStateFromGenesis(db, blockState, voters) + require.NoError(t, err) + + forks := tt.generateForks(t, gs.blockState) + + if tt.changes != nil { + tt.changes(gs, forks) + } + + selectedFork := forks[tt.finalizedHeader[0]] + selectedFinalizedHeader := selectedFork[tt.finalizedHeader[1]] + + err = gs.ApplyScheduledChanges(selectedFinalizedHeader) + if tt.wantErr != nil { + require.EqualError(t, err, tt.wantErr.Error()) + } else { + require.NoError(t, err) + + // ensure the forced changes and scheduled changes + // are descendant of the latest finalized header + for _, forcedChange := range gs.forcedChanges { + isDescendant, err := gs.blockState.IsDescendantOf( + selectedFinalizedHeader.Hash(), forcedChange.announcingHeader.Hash()) + + require.NoError(t, err) + require.True(t, isDescendant) + } + + assertDescendantChildren(t, + selectedFinalizedHeader.Hash(), gs.blockState.IsDescendantOf, gs.scheduledChangeRoots) + } + + require.Len(t, gs.forcedChanges, tt.expectedForcedChangesLen) + require.Len(t, gs.scheduledChangeRoots, tt.expectedScheduledChangeRootsLen) + + currentSetID, err := gs.GetCurrentSetID() + require.NoError(t, err) + require.Equal(t, tt.expectedSetID, currentSetID) + + currentVoters, err := gs.GetAuthorities(currentSetID) + require.NoError(t, err) + require.Equal(t, tt.expectedAuthoritySet, currentVoters) + + blockNumber, err := gs.GetSetIDChange(currentSetID) + require.NoError(t, err) + require.Equal(t, tt.changeSetIDAt, blockNumber) + }) + } } From bfece81e8245ea0ee06cd132ac49e29682858644 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Wed, 4 May 2022 15:35:50 -0400 Subject: [PATCH 41/93] chore: add logs to forced changes import and finalization --- dot/state/grandpa.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index f89db4ab42..784f715a2b 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -219,6 +219,7 @@ func (s *GrandpaState) addForcedChange(header *types.Header, fc types.GrandpaFor s.forcedChanges = append(s.forcedChanges, pendingChange) sort.Sort(s.forcedChanges) + logger.Debugf("there are now %d possible forced changes", len(s.forcedChanges)) return nil } @@ -238,10 +239,6 @@ func (s *GrandpaState) addScheduledChange(header *types.Header, sc types.Grandpa } func (s *GrandpaState) importScheduledChange(pendingChange *pendingChange) error { - defer func() { - logger.Debugf("there are now %d possible scheduled changes (roots)", len(s.scheduledChangeRoots)) - }() - highestFinalizedHeader, err := s.blockState.GetHighestFinalisedHeader() if err != nil { return fmt.Errorf("cannot get highest finalized header: %w", err) @@ -272,6 +269,8 @@ func (s *GrandpaState) importScheduledChange(pendingChange *pendingChange) error } s.scheduledChangeRoots = append(s.scheduledChangeRoots, pendingChangeNode) + logger.Debugf("there are now %d possible scheduled changes (roots)", len(s.scheduledChangeRoots)) + return nil } @@ -406,7 +405,7 @@ func (s *GrandpaState) ApplyScheduledChanges(finalizedHeader *types.Header) erro return nil } - logger.Debugf("scheduled changes: change to apply: %s", changeToApply) + logger.Debugf("applying scheduled change: %s", changeToApply) newSetID, err := s.IncrementSetID() if err != nil { @@ -470,7 +469,6 @@ func (s *GrandpaState) ApplyForcedChanges(importedBlockHeader *types.Header) err // checking for dependant pending scheduled changes for _, scheduled := range s.scheduledChangeRoots { - if scheduled.change.effectiveNumber() > uint(bestFinalizedNumber) { continue } @@ -486,6 +484,8 @@ func (s *GrandpaState) ApplyForcedChanges(importedBlockHeader *types.Header) err } } + logger.Debugf("applying forced change: %s", forcedChange) + // send the telemetry s messages here // afg.applying_forced_authority_set_change From c17d75926f84ef25b81f137ec7bd921ccf513771 Mon Sep 17 00:00:00 2001 From: jimboj Date: Tue, 19 Apr 2022 16:36:08 -0600 Subject: [PATCH 42/93] upgrate deps again --- dot/core/service.go | 4 ++-- dot/rpc/modules/system.go | 2 +- go.mod | 3 ++- go.sum | 14 +++++++++++++- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/dot/core/service.go b/dot/core/service.go index e4bc6d846a..cb2552eefb 100644 --- a/dot/core/service.go +++ b/dot/core/service.go @@ -22,8 +22,8 @@ import ( "github.com/ChainSafe/gossamer/lib/runtime/wasmer" "github.com/ChainSafe/gossamer/lib/services" "github.com/ChainSafe/gossamer/lib/transaction" - cscale "github.com/centrifuge/go-substrate-rpc-client/v3/scale" - ctypes "github.com/centrifuge/go-substrate-rpc-client/v3/types" + cscale "github.com/centrifuge/go-substrate-rpc-client/v4/scale" + ctypes "github.com/centrifuge/go-substrate-rpc-client/v4/types" ) var ( diff --git a/dot/rpc/modules/system.go b/dot/rpc/modules/system.go index 79c7e6e6af..89b33992a0 100644 --- a/dot/rpc/modules/system.go +++ b/dot/rpc/modules/system.go @@ -14,7 +14,7 @@ import ( "github.com/ChainSafe/gossamer/lib/crypto" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/btcsuite/btcutil/base58" - ctypes "github.com/centrifuge/go-substrate-rpc-client/v3/types" + ctypes "github.com/centrifuge/go-substrate-rpc-client/v4/types" ) // SystemModule is an RPC module providing access to core API points diff --git a/go.mod b/go.mod index 90fb5da92d..b1663724da 100644 --- a/go.mod +++ b/go.mod @@ -47,6 +47,7 @@ require ( ) require ( + github.com/centrifuge/go-substrate-rpc-client/v4 v4.0.0 github.com/prometheus/client_golang v1.12.1 github.com/prometheus/client_model v0.2.0 ) @@ -160,7 +161,7 @@ require ( github.com/polydawn/refmt v0.0.0-20190807091052-3d65705ee9f1 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect - github.com/rs/cors v1.7.0 // indirect + github.com/rs/cors v1.8.0 // indirect github.com/russross/blackfriday/v2 v2.0.1 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect diff --git a/go.sum b/go.sum index d45c9d20ad..dfd4d62f96 100644 --- a/go.sum +++ b/go.sum @@ -159,6 +159,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/centrifuge/go-substrate-rpc-client/v3 v3.0.2 h1:SQNaOeTmW2y2fmJgR5a7KIozjaOYi34GxafQ4efGc5U= github.com/centrifuge/go-substrate-rpc-client/v3 v3.0.2/go.mod h1:ZYSX8OuIJgZ9aVdKLhIi1G4Rj42Ys4nZNsWW70yfCJc= +github.com/centrifuge/go-substrate-rpc-client/v4 v4.0.0 h1:/t8Aw7d3rCu1uqYFFG2JIoYK/W6/Af5C1+WNF6XyYL8= +github.com/centrifuge/go-substrate-rpc-client/v4 v4.0.0/go.mod h1:MDzvG8lkzMGRaO4qzvxdfJtlDtukRPqNVWG9HJybVt0= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -299,6 +301,8 @@ github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= @@ -323,8 +327,10 @@ github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34 github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= @@ -661,6 +667,7 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+ github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -1187,8 +1194,9 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/cors v1.8.0 h1:P2KMzcFwrPoSjkF1WLRPsp3UMLyql8L4v9hQpVeK5so= +github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM= github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= @@ -1288,7 +1296,9 @@ github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoi github.com/twitchyliquid64/golang-asm v0.0.0-20190126203739-365674df15fc h1:RTUQlKzoZZVG3umWNzOYeFecQLIh+dbxXvJp1zPQJTI= github.com/twitchyliquid64/golang-asm v0.0.0-20190126203739-365674df15fc/go.mod h1:NoCfSFWosfqMqmmD7hApkirIK9ozpHjxRnRxs1l413A= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.8 h1:9ic0a+f2TCJ5tSbVRX/FSSCIHJacFLYxcuNexNMJF8Q= @@ -1821,6 +1831,8 @@ gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= From c92730f91cf7f60fe6a474c894ac3cb992db767e Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Thu, 12 May 2022 12:28:12 -0400 Subject: [PATCH 43/93] wip: adding tests to forced changes digests --- dot/digest/digest.go | 78 +++++++++++++++-- dot/digest/digest_test.go | 37 ++++++++ dot/digest/mock_grandpa_test.go | 150 ++++++++++++++++++++++++++++++++ dot/state/grandpa.go | 101 ++------------------- dot/state/grandpa_changes.go | 94 ++++++++++++++++++++ lib/grandpa/grandpa.go | 6 +- 6 files changed, 363 insertions(+), 103 deletions(-) create mode 100644 dot/digest/mock_grandpa_test.go create mode 100644 dot/state/grandpa_changes.go diff --git a/dot/digest/digest.go b/dot/digest/digest.go index 1482161f79..c453ca2c21 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -78,18 +78,81 @@ func (h *Handler) Stop() error { // HandleDigests handles consensus digests for an imported block func (h *Handler) HandleDigests(header *types.Header) { - for i, d := range header.Digest.Types { - val, ok := d.Value().(types.ConsensusDigest) + digestTypes := ToConsensusDigest(header.Digest.Types) + digestTypes, err := ignoreGRANDPAMultipleDigests(digestTypes) + if err != nil { + h.logger.Errorf("cannot ignore multiple GRANDPA digests: %w", err) + return + } + + for i, digest := range digestTypes { + err := h.handleConsensusDigest(&digest, header) + if err != nil { + h.logger.Errorf("cannot handle digest for block number %d, index %d, digest %s: %s", + header.Number, i, digest, err) + } + } +} + +// ToConsensusDigest will parse an []scale.VaryingDataType slice into []types.ConsensusDigest +func ToConsensusDigest(scaleVaryingTypes []scale.VaryingDataType) []types.ConsensusDigest { + consensusDigests := make([]types.ConsensusDigest, 0, len(scaleVaryingTypes)) + + for _, d := range scaleVaryingTypes { + digest, ok := d.Value().(types.ConsensusDigest) if !ok { continue } - err := h.handleConsensusDigest(&val, header) - if err != nil { - h.logger.Errorf("cannot handle digest for block number %d, index %d, digest %s: %s", - header.Number, i, d.Value(), err) + switch digest.ConsensusEngineID { + case types.GrandpaEngineID: + consensusDigests = append(consensusDigests, digest) + case types.BabeEngineID: + consensusDigests = append(consensusDigests, digest) } } + + return consensusDigests +} + +func ignoreGRANDPAMultipleDigests(digests []types.ConsensusDigest) ([]types.ConsensusDigest, error) { + var hasForcedChange bool + scheduledChangesIndex := make(map[int]struct{}, len(digests)) + + for idx, digest := range digests { + switch digest.ConsensusEngineID { + case types.GrandpaEngineID: + data := types.NewGrandpaConsensusDigest() + err := scale.Unmarshal(digest.Data, &data) + if err != nil { + return nil, fmt.Errorf("cannot unmarshal GRANDPA consensus digest: %w", err) + } + + switch data.Value().(type) { + case types.GrandpaScheduledChange: + scheduledChangesIndex[idx] = struct{}{} + case types.GrandpaForcedChange: + hasForcedChange = true + default: + } + } + } + + if hasForcedChange { + digestsWithoutScheduled := make([]types.ConsensusDigest, len(digests)-len(scheduledChangesIndex)) + for idx, digests := range digests { + _, ok := scheduledChangesIndex[idx] + if ok { + continue + } + + digestsWithoutScheduled = append(digestsWithoutScheduled, digests) + } + + return digestsWithoutScheduled, nil + } + + return digests, nil } func (h *Handler) handleConsensusDigest(d *types.ConsensusDigest, header *types.Header) error { @@ -132,7 +195,6 @@ func (h *Handler) handleBabeConsensusDigest(digest scale.VaryingDataType, header return nil case types.BABEOnDisabled: - h.logger.Debug("handling BABEOnDisabled") return nil case types.NextConfigData: @@ -190,7 +252,7 @@ func (h *Handler) handleBlockFinalisation(ctx context.Context) { err = h.grandpaState.ApplyScheduledChanges(&info.Header) if err != nil { - h.logger.Errorf("failed to apply standard scheduled changes on block finalisation: %w", err) + h.logger.Errorf("failed to apply scheduled change on block finalisation: %w", err) } case <-ctx.Done(): diff --git a/dot/digest/digest_test.go b/dot/digest/digest_test.go index 6fe8995b38..6530059f86 100644 --- a/dot/digest/digest_test.go +++ b/dot/digest/digest_test.go @@ -27,6 +27,7 @@ import ( ) //go:generate mockgen -destination=mock_telemetry_test.go -package $GOPACKAGE github.com/ChainSafe/gossamer/dot/telemetry Client +//go:generate mockgen -destination=mock_grandpa_test.go -package $GOPACKAGE . GrandpaState func newTestHandler(t *testing.T) (*Handler, *state.Service) { testDatadirPath := t.TempDir() @@ -226,6 +227,42 @@ func TestHandler_GrandpaPauseAndResume(t *testing.T) { require.Equal(t, uint(r.Delay+p.Delay), nextResume) } +func TestMultipleGRANDPADigests_ShouldIncludeJustForcedChanges(t *testing.T) { + forcedChange := types.GrandpaForcedChange{} + scheduledChange := types.GrandpaForcedChange{} + + var digest = types.NewGrandpaConsensusDigest() + require.NoError(t, digest.Set(forcedChange)) + require.NoError(t, digest.Set(scheduledChange)) + + data, err := scale.Marshal(digest) + require.NoError(t, err) + + consensusDigest := &types.ConsensusDigest{ + ConsensusEngineID: types.GrandpaEngineID, + Data: data, + } + + digestItem := types.NewDigest() + digest.Set(consensusDigest) + + header := &types.Header{ + Digest: digestItem, + } + + handler, _ := newTestHandler(t) + ctrl := gomock.NewController(t) + + expectedGRANDPADigest := types.NewGrandpaConsensusDigest() + require.NoError(t, expectedGRANDPADigest.Set(forcedChange)) + + grandpaState := NewMockGrandpaState(ctrl) + grandpaState.EXPECT().HandleGRANDPADigest(header, expectedGRANDPADigest).Return(nil) + + handler.grandpaState = grandpaState + handler.HandleDigests(header) +} + // func TestNextGrandpaAuthorityChange_OneChange(t *testing.T) { // handler, _ := newTestHandler(t) // handler.Start() diff --git a/dot/digest/mock_grandpa_test.go b/dot/digest/mock_grandpa_test.go new file mode 100644 index 0000000000..e71def0777 --- /dev/null +++ b/dot/digest/mock_grandpa_test.go @@ -0,0 +1,150 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ChainSafe/gossamer/dot/digest (interfaces: GrandpaState) + +// Package digest is a generated GoMock package. +package digest + +import ( + reflect "reflect" + + types "github.com/ChainSafe/gossamer/dot/types" + scale "github.com/ChainSafe/gossamer/pkg/scale" + gomock "github.com/golang/mock/gomock" +) + +// MockGrandpaState is a mock of GrandpaState interface. +type MockGrandpaState struct { + ctrl *gomock.Controller + recorder *MockGrandpaStateMockRecorder +} + +// MockGrandpaStateMockRecorder is the mock recorder for MockGrandpaState. +type MockGrandpaStateMockRecorder struct { + mock *MockGrandpaState +} + +// NewMockGrandpaState creates a new mock instance. +func NewMockGrandpaState(ctrl *gomock.Controller) *MockGrandpaState { + mock := &MockGrandpaState{ctrl: ctrl} + mock.recorder = &MockGrandpaStateMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockGrandpaState) EXPECT() *MockGrandpaStateMockRecorder { + return m.recorder +} + +// ApplyForcedChanges mocks base method. +func (m *MockGrandpaState) ApplyForcedChanges(arg0 *types.Header) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ApplyForcedChanges", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// ApplyForcedChanges indicates an expected call of ApplyForcedChanges. +func (mr *MockGrandpaStateMockRecorder) ApplyForcedChanges(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApplyForcedChanges", reflect.TypeOf((*MockGrandpaState)(nil).ApplyForcedChanges), arg0) +} + +// ApplyScheduledChanges mocks base method. +func (m *MockGrandpaState) ApplyScheduledChanges(arg0 *types.Header) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ApplyScheduledChanges", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// ApplyScheduledChanges indicates an expected call of ApplyScheduledChanges. +func (mr *MockGrandpaStateMockRecorder) ApplyScheduledChanges(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApplyScheduledChanges", reflect.TypeOf((*MockGrandpaState)(nil).ApplyScheduledChanges), arg0) +} + +// GetCurrentSetID mocks base method. +func (m *MockGrandpaState) GetCurrentSetID() (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCurrentSetID") + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCurrentSetID indicates an expected call of GetCurrentSetID. +func (mr *MockGrandpaStateMockRecorder) GetCurrentSetID() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentSetID", reflect.TypeOf((*MockGrandpaState)(nil).GetCurrentSetID)) +} + +// HandleGRANDPADigest mocks base method. +func (m *MockGrandpaState) HandleGRANDPADigest(arg0 *types.Header, arg1 scale.VaryingDataType) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HandleGRANDPADigest", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// HandleGRANDPADigest indicates an expected call of HandleGRANDPADigest. +func (mr *MockGrandpaStateMockRecorder) HandleGRANDPADigest(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleGRANDPADigest", reflect.TypeOf((*MockGrandpaState)(nil).HandleGRANDPADigest), arg0, arg1) +} + +// IncrementSetID mocks base method. +func (m *MockGrandpaState) IncrementSetID() (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IncrementSetID") + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// IncrementSetID indicates an expected call of IncrementSetID. +func (mr *MockGrandpaStateMockRecorder) IncrementSetID() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IncrementSetID", reflect.TypeOf((*MockGrandpaState)(nil).IncrementSetID)) +} + +// SetNextChange mocks base method. +func (m *MockGrandpaState) SetNextChange(arg0 []types.GrandpaVoter, arg1 uint) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetNextChange", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetNextChange indicates an expected call of SetNextChange. +func (mr *MockGrandpaStateMockRecorder) SetNextChange(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetNextChange", reflect.TypeOf((*MockGrandpaState)(nil).SetNextChange), arg0, arg1) +} + +// SetNextPause mocks base method. +func (m *MockGrandpaState) SetNextPause(arg0 uint) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetNextPause", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetNextPause indicates an expected call of SetNextPause. +func (mr *MockGrandpaStateMockRecorder) SetNextPause(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetNextPause", reflect.TypeOf((*MockGrandpaState)(nil).SetNextPause), arg0) +} + +// SetNextResume mocks base method. +func (m *MockGrandpaState) SetNextResume(arg0 uint) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetNextResume", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetNextResume indicates an expected call of SetNextResume. +func (mr *MockGrandpaStateMockRecorder) SetNextResume(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetNextResume", reflect.TypeOf((*MockGrandpaState)(nil).SetNextResume), arg0) +} diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 784f715a2b..a8b5cea62d 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -16,16 +16,6 @@ import ( "github.com/ChainSafe/gossamer/pkg/scale" ) -var ( - genesisSetID = uint64(0) - grandpaPrefix = "grandpa" - authoritiesPrefix = []byte("auth") - setIDChangePrefix = []byte("change") - pauseKey = []byte("pause") - resumeKey = []byte("resume") - currentSetIDKey = []byte("setID") -) - var ( errPendingScheduledChanges = errors.New("pending scheduled changes needs to be applied") errDuplicateHashes = errors.New("duplicated hashes") @@ -36,88 +26,15 @@ var ( ErrNoChanges = errors.New("cannot get the next authority change block number") ) -type pendingChange struct { - bestFinalizedNumber uint32 - delay uint32 - nextAuthorities []types.Authority - announcingHeader *types.Header -} - -func (p pendingChange) String() string { - return fmt.Sprintf("announcing header: %s (%d), delay: %d, next authorities: %d", - p.announcingHeader.Hash(), p.announcingHeader.Number, p.delay, len(p.nextAuthorities)) -} - -func (p *pendingChange) effectiveNumber() uint { - return p.announcingHeader.Number + uint(p.delay) -} - -// appliedBefore compares the effective number between two pending changes -// and returns true if: -// - the current pending change is applied before the target -// - the target is nil and the current contains a value -func (p *pendingChange) appliedBefore(target *pendingChange) bool { - if p != nil && target != nil { - return p.effectiveNumber() < target.effectiveNumber() - } - - return p != nil -} - -type isDescendantOfFunc func(parent, child common.Hash) (bool, error) - -type pendingChangeNode struct { - change *pendingChange - nodes []*pendingChangeNode -} - -func (c *pendingChangeNode) importScheduledChange(blockHash common.Hash, blockNumber uint, pendingChange *pendingChange, - isDescendantOf isDescendantOfFunc) (imported bool, err error) { - announcingHash := c.change.announcingHeader.Hash() - - if blockHash.Equal(announcingHash) { - return false, errDuplicateHashes - } - - if blockNumber <= c.change.announcingHeader.Number { - return false, nil - } - - for _, childrenNodes := range c.nodes { - imported, err := childrenNodes.importScheduledChange(blockHash, blockNumber, pendingChange, isDescendantOf) - if err != nil { - return false, err - } - - if imported { - return true, nil - } - } - - isDescendant, err := isDescendantOf(announcingHash, blockHash) - if err != nil { - return false, fmt.Errorf("cannot define ancestry: %w", err) - } - - if !isDescendant { - return false, nil - } - - pendingChangeNode := &pendingChangeNode{change: pendingChange, nodes: []*pendingChangeNode{}} - c.nodes = append(c.nodes, pendingChangeNode) - return true, nil -} - -type orderedPendingChanges []*pendingChange - -func (o orderedPendingChanges) Len() int { return len(o) } -func (o orderedPendingChanges) Swap(i, j int) { o[i], o[j] = o[j], o[i] } - -// Less order by effective number and then by block number -func (o orderedPendingChanges) Less(i, j int) bool { - return o[i].effectiveNumber() < o[j].effectiveNumber() && - o[i].announcingHeader.Number < o[j].announcingHeader.Number -} +var ( + genesisSetID = uint64(0) + grandpaPrefix = "grandpa" + authoritiesPrefix = []byte("auth") + setIDChangePrefix = []byte("change") + pauseKey = []byte("pause") + resumeKey = []byte("resume") + currentSetIDKey = []byte("setID") +) // GrandpaState tracks information related to grandpa type GrandpaState struct { diff --git a/dot/state/grandpa_changes.go b/dot/state/grandpa_changes.go new file mode 100644 index 0000000000..79cda030d1 --- /dev/null +++ b/dot/state/grandpa_changes.go @@ -0,0 +1,94 @@ +// Copyright 2022 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package state + +import ( + "fmt" + + "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/common" +) + +type isDescendantOfFunc func(parent, child common.Hash) (bool, error) + +type pendingChange struct { + bestFinalizedNumber uint32 + delay uint32 + nextAuthorities []types.Authority + announcingHeader *types.Header +} + +func (p pendingChange) String() string { + return fmt.Sprintf("announcing header: %s (%d), delay: %d, next authorities: %d", + p.announcingHeader.Hash(), p.announcingHeader.Number, p.delay, len(p.nextAuthorities)) +} + +func (p *pendingChange) effectiveNumber() uint { + return p.announcingHeader.Number + uint(p.delay) +} + +// appliedBefore compares the effective number between two pending changes +// and returns true if: +// - the current pending change is applied before the target +// - the target is nil and the current contains a value +func (p *pendingChange) appliedBefore(target *pendingChange) bool { + if p != nil && target != nil { + return p.effectiveNumber() < target.effectiveNumber() + } + + return p != nil +} + +type orderedPendingChanges []*pendingChange + +func (o orderedPendingChanges) Len() int { return len(o) } +func (o orderedPendingChanges) Swap(i, j int) { o[i], o[j] = o[j], o[i] } + +// Less order by effective number and then by block number +func (o orderedPendingChanges) Less(i, j int) bool { + return o[i].effectiveNumber() < o[j].effectiveNumber() && + o[i].announcingHeader.Number < o[j].announcingHeader.Number +} + +type pendingChangeNode struct { + change *pendingChange + nodes []*pendingChangeNode +} + +func (c *pendingChangeNode) importScheduledChange(blockHash common.Hash, blockNumber uint, pendingChange *pendingChange, + isDescendantOf isDescendantOfFunc) (imported bool, err error) { + announcingHash := c.change.announcingHeader.Hash() + + if blockHash.Equal(announcingHash) { + return false, errDuplicateHashes + } + + if blockNumber <= c.change.announcingHeader.Number { + return false, nil + } + + for _, childrenNodes := range c.nodes { + imported, err := childrenNodes.importScheduledChange(blockHash, blockNumber, pendingChange, isDescendantOf) + if err != nil { + return false, err + } + + if imported { + return true, nil + } + } + + isDescendant, err := isDescendantOf(announcingHash, blockHash) + if err != nil { + return false, fmt.Errorf("cannot define ancestry: %w", err) + } + + if !isDescendant { + return false, nil + } + + pendingChangeNode := &pendingChangeNode{change: pendingChange, nodes: []*pendingChangeNode{}} + c.nodes = append(c.nodes, pendingChangeNode) + return true, nil +} diff --git a/lib/grandpa/grandpa.go b/lib/grandpa/grandpa.go index b2b177faf8..a61d1381af 100644 --- a/lib/grandpa/grandpa.go +++ b/lib/grandpa/grandpa.go @@ -683,7 +683,7 @@ func (s *Service) deleteVote(key ed25519.PublicKeyBytes, stage Subround) { func (s *Service) determinePreVote() (*Vote, error) { var vote *Vote - beastBlockHeader, err := s.blockState.BestBlockHeader() + bestBlockHeader, err := s.blockState.BestBlockHeader() if err != nil { return nil, fmt.Errorf("cannot get best block header: %w", err) } @@ -697,10 +697,10 @@ func (s *Service) determinePreVote() (*Vote, error) { if has && prm.Vote.Number >= uint32(s.head.Number) { vote = &prm.Vote } else { - vote = NewVoteFromHeader(beastBlockHeader) + vote = NewVoteFromHeader(bestBlockHeader) } - nextChange, err := s.grandpaState.NextGrandpaAuthorityChange(beastBlockHeader.Hash()) + nextChange, err := s.grandpaState.NextGrandpaAuthorityChange(bestBlockHeader.Hash()) if errors.Is(err, state.ErrNoChanges) { return vote, nil } else if err != nil { From 315445ab716b05d6962734125c389abb99cec4bd Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Thu, 12 May 2022 16:51:14 -0400 Subject: [PATCH 44/93] chore: ignoring scheduled changes in the presence of forced changes in the same block --- dot/digest/digest.go | 27 ++--- dot/digest/digest_test.go | 225 +++++++++++++++----------------------- 2 files changed, 101 insertions(+), 151 deletions(-) diff --git a/dot/digest/digest.go b/dot/digest/digest.go index c453ca2c21..6d17ea22fc 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -78,14 +78,18 @@ func (h *Handler) Stop() error { // HandleDigests handles consensus digests for an imported block func (h *Handler) HandleDigests(header *types.Header) { - digestTypes := ToConsensusDigest(header.Digest.Types) - digestTypes, err := ignoreGRANDPAMultipleDigests(digestTypes) + consensusDigests := ToConsensusDigests(header.Digest.Types) + consensusDigests, err := checkForGRANDPAForcedChanges(consensusDigests) if err != nil { h.logger.Errorf("cannot ignore multiple GRANDPA digests: %w", err) return } - for i, digest := range digestTypes { + for i := range consensusDigests { + // avoiding implicit memory aliasing in for loop, since: + // for _, digest := range consensusDigests { &digest } + // I'm using the address of a loop variable + digest := consensusDigests[i] err := h.handleConsensusDigest(&digest, header) if err != nil { h.logger.Errorf("cannot handle digest for block number %d, index %d, digest %s: %s", @@ -94,8 +98,8 @@ func (h *Handler) HandleDigests(header *types.Header) { } } -// ToConsensusDigest will parse an []scale.VaryingDataType slice into []types.ConsensusDigest -func ToConsensusDigest(scaleVaryingTypes []scale.VaryingDataType) []types.ConsensusDigest { +// ToConsensusDigests will parse an []scale.VaryingDataType slice into []types.ConsensusDigest +func ToConsensusDigests(scaleVaryingTypes []scale.VaryingDataType) []types.ConsensusDigest { consensusDigests := make([]types.ConsensusDigest, 0, len(scaleVaryingTypes)) for _, d := range scaleVaryingTypes { @@ -105,9 +109,7 @@ func ToConsensusDigest(scaleVaryingTypes []scale.VaryingDataType) []types.Consen } switch digest.ConsensusEngineID { - case types.GrandpaEngineID: - consensusDigests = append(consensusDigests, digest) - case types.BabeEngineID: + case types.GrandpaEngineID, types.BabeEngineID: consensusDigests = append(consensusDigests, digest) } } @@ -115,7 +117,7 @@ func ToConsensusDigest(scaleVaryingTypes []scale.VaryingDataType) []types.Consen return consensusDigests } -func ignoreGRANDPAMultipleDigests(digests []types.ConsensusDigest) ([]types.ConsensusDigest, error) { +func checkForGRANDPAForcedChanges(digests []types.ConsensusDigest) ([]types.ConsensusDigest, error) { var hasForcedChange bool scheduledChangesIndex := make(map[int]struct{}, len(digests)) @@ -133,20 +135,19 @@ func ignoreGRANDPAMultipleDigests(digests []types.ConsensusDigest) ([]types.Cons scheduledChangesIndex[idx] = struct{}{} case types.GrandpaForcedChange: hasForcedChange = true - default: } } } if hasForcedChange { - digestsWithoutScheduled := make([]types.ConsensusDigest, len(digests)-len(scheduledChangesIndex)) - for idx, digests := range digests { + digestsWithoutScheduled := make([]types.ConsensusDigest, 0, len(digests)-len(scheduledChangesIndex)) + for idx, digest := range digests { _, ok := scheduledChangesIndex[idx] if ok { continue } - digestsWithoutScheduled = append(digestsWithoutScheduled, digests) + digestsWithoutScheduled = append(digestsWithoutScheduled, digest) } return digestsWithoutScheduled, nil diff --git a/dot/digest/digest_test.go b/dot/digest/digest_test.go index 6530059f86..e7ccc16ab7 100644 --- a/dot/digest/digest_test.go +++ b/dot/digest/digest_test.go @@ -228,154 +228,103 @@ func TestHandler_GrandpaPauseAndResume(t *testing.T) { } func TestMultipleGRANDPADigests_ShouldIncludeJustForcedChanges(t *testing.T) { - forcedChange := types.GrandpaForcedChange{} - scheduledChange := types.GrandpaForcedChange{} + tests := map[string]struct { + digestsTypes []scale.VaryingDataTypeValue + expectedHandled []scale.VaryingDataTypeValue + }{ + "forced_and_scheduled_changes_same_block": { + digestsTypes: []scale.VaryingDataTypeValue{ + types.GrandpaForcedChange{}, + types.GrandpaScheduledChange{}, + }, + expectedHandled: []scale.VaryingDataTypeValue{ + types.GrandpaForcedChange{}, + }, + }, + "only_scheduled_change_in_block": { + digestsTypes: []scale.VaryingDataTypeValue{ + types.GrandpaScheduledChange{}, + }, + expectedHandled: []scale.VaryingDataTypeValue{ + types.GrandpaScheduledChange{}, + }, + }, + "more_than_one_forced_changes_in_block": { + digestsTypes: []scale.VaryingDataTypeValue{ + types.GrandpaForcedChange{}, + types.GrandpaForcedChange{}, + types.GrandpaForcedChange{}, + types.GrandpaScheduledChange{}, + }, + expectedHandled: []scale.VaryingDataTypeValue{ + types.GrandpaForcedChange{}, + types.GrandpaForcedChange{}, + types.GrandpaForcedChange{}, + }, + }, + "multiple_consensus_digests_in_block": { + digestsTypes: []scale.VaryingDataTypeValue{ + types.GrandpaOnDisabled{}, + types.GrandpaPause{}, + types.GrandpaResume{}, + types.GrandpaForcedChange{}, + types.GrandpaScheduledChange{}, + }, + expectedHandled: []scale.VaryingDataTypeValue{ + types.GrandpaOnDisabled{}, + types.GrandpaPause{}, + types.GrandpaResume{}, + types.GrandpaForcedChange{}, + }, + }, + } - var digest = types.NewGrandpaConsensusDigest() - require.NoError(t, digest.Set(forcedChange)) - require.NoError(t, digest.Set(scheduledChange)) + for tname, tt := range tests { + tt := tt + t.Run(tname, func(t *testing.T) { + digests := types.NewDigest() - data, err := scale.Marshal(digest) - require.NoError(t, err) + for _, item := range tt.digestsTypes { + var digest = types.NewGrandpaConsensusDigest() + require.NoError(t, digest.Set(item)) - consensusDigest := &types.ConsensusDigest{ - ConsensusEngineID: types.GrandpaEngineID, - Data: data, - } + data, err := scale.Marshal(digest) + require.NoError(t, err) - digestItem := types.NewDigest() - digest.Set(consensusDigest) + consensusDigest := types.ConsensusDigest{ + ConsensusEngineID: types.GrandpaEngineID, + Data: data, + } - header := &types.Header{ - Digest: digestItem, - } + require.NoError(t, digests.Add(consensusDigest)) + } - handler, _ := newTestHandler(t) - ctrl := gomock.NewController(t) + header := &types.Header{ + Digest: digests, + } - expectedGRANDPADigest := types.NewGrandpaConsensusDigest() - require.NoError(t, expectedGRANDPADigest.Set(forcedChange)) + handler, _ := newTestHandler(t) + ctrl := gomock.NewController(t) + grandpaState := NewMockGrandpaState(ctrl) - grandpaState := NewMockGrandpaState(ctrl) - grandpaState.EXPECT().HandleGRANDPADigest(header, expectedGRANDPADigest).Return(nil) + for _, item := range tt.expectedHandled { + var digest = types.NewGrandpaConsensusDigest() + require.NoError(t, digest.Set(item)) - handler.grandpaState = grandpaState - handler.HandleDigests(header) -} + data, err := scale.Marshal(digest) + require.NoError(t, err) + + expected := types.NewGrandpaConsensusDigest() + require.NoError(t, scale.Unmarshal(data, &expected)) -// func TestNextGrandpaAuthorityChange_OneChange(t *testing.T) { -// handler, _ := newTestHandler(t) -// handler.Start() -// defer handler.Stop() - -// const block uint = 3 -// sc := types.GrandpaScheduledChange{ -// Auths: []types.GrandpaAuthoritiesRaw{}, -// Delay: uint32(block), -// } - -// var digest = types.NewGrandpaConsensusDigest() -// err := digest.Set(sc) -// require.NoError(t, err) - -// data, err := scale.Marshal(digest) -// require.NoError(t, err) - -// d := &types.ConsensusDigest{ -// ConsensusEngineID: types.GrandpaEngineID, -// Data: data, -// } -// header := &types.Header{ -// Number: 1, -// } - -// err = handler.handleConsensusDigest(d, header) -// require.NoError(t, err) - -// next := handler.NextGrandpaAuthorityChange() -// require.Equal(t, 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) -// } - -// TODO: move this test to dot/state/grandpa.go -// func TestNextGrandpaAuthorityChange_MultipleChanges(t *testing.T) { -// handler, _ := newTestHandler(t) -// handler.Start() -// defer handler.Stop() - -// kr, err := keystore.NewEd25519Keyring() -// require.NoError(t, err) - -// later := uint32(6) -// sc := types.GrandpaScheduledChange{ -// Auths: []types.GrandpaAuthoritiesRaw{}, -// Delay: later, -// } - -// var digest = types.NewGrandpaConsensusDigest() -// err = digest.Set(sc) -// require.NoError(t, err) - -// data, err := scale.Marshal(digest) -// require.NoError(t, err) - -// d := &types.ConsensusDigest{ -// ConsensusEngineID: types.GrandpaEngineID, -// Data: data, -// } - -// header := &types.Header{ -// Number: 1, -// } - -// err = handler.handleConsensusDigest(d, header) -// require.NoError(t, err) - -// nextSetID := uint64(1) -// auths, err := handler.grandpaState.(*state.GrandpaState).GetAuthorities(nextSetID) -// require.NoError(t, err) -// expected, err := types.NewGrandpaVotersFromAuthoritiesRaw(sc.Auths) -// require.NoError(t, err) -// require.Equal(t, expected, auths) - -// const earlier uint = 4 -// fc := types.GrandpaForcedChange{ -// Auths: []types.GrandpaAuthoritiesRaw{ -// {Key: kr.Alice().Public().(*ed25519.PublicKey).AsBytes(), ID: 0}, -// }, -// Delay: uint32(earlier), -// } - -// digest = types.NewGrandpaConsensusDigest() -// err = digest.Set(fc) -// require.NoError(t, err) - -// data, err = scale.Marshal(digest) -// require.NoError(t, err) - -// d = &types.ConsensusDigest{ -// ConsensusEngineID: types.GrandpaEngineID, -// Data: data, -// } - -// err = handler.handleConsensusDigest(d, header) -// require.NoError(t, err) - -// next := handler.NextGrandpaAuthorityChange() -// require.Equal(t, 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) -// } + grandpaState.EXPECT().HandleGRANDPADigest(header, expected).Return(nil) + } + + handler.grandpaState = grandpaState + handler.HandleDigests(header) + }) + } +} func TestHandler_HandleBABEOnDisabled(t *testing.T) { handler, _ := newTestHandler(t) From e55524153b0ae286767321ce5ae230d81fe1c854 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Wed, 18 May 2022 13:29:47 -0400 Subject: [PATCH 45/93] chore: remove unneeded highest block verification while importing change --- dot/state/grandpa.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index a8b5cea62d..63771bf2a3 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -156,15 +156,6 @@ func (s *GrandpaState) addScheduledChange(header *types.Header, sc types.Grandpa } func (s *GrandpaState) importScheduledChange(pendingChange *pendingChange) error { - highestFinalizedHeader, err := s.blockState.GetHighestFinalisedHeader() - if err != nil { - return fmt.Errorf("cannot get highest finalized header: %w", err) - } - - if pendingChange.announcingHeader.Number <= highestFinalizedHeader.Number { - return errLowerThanBestFinalized - } - for _, root := range s.scheduledChangeRoots { imported, err := root.importScheduledChange(pendingChange.announcingHeader.Hash(), pendingChange.announcingHeader.Number, pendingChange, s.blockState.IsDescendantOf) From cbba07bfc6783ae3627aebcd28913ff113e4616d Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Wed, 18 May 2022 13:35:15 -0400 Subject: [PATCH 46/93] chore: fix unit tests --- dot/state/grandpa_test.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/dot/state/grandpa_test.go b/dot/state/grandpa_test.go index 2bc8901e51..715487dbd7 100644 --- a/dot/state/grandpa_test.go +++ b/dot/state/grandpa_test.go @@ -202,13 +202,6 @@ func TestAddScheduledChangesKeepTheRightForkTree(t *testing.T) { }, expectedRoots: 3, }, - "add_scheduled_change_before_highest_finalized_header": { - headersWithScheduledChanges: []headersToAdd{ - {header: chainA[3], wantErr: errLowerThanBestFinalized}, - }, - highestFinalizedHeader: chainA[5], - expectedRoots: 0, - }, "add_scheduled_changes_with_same_hash": { headersWithScheduledChanges: []headersToAdd{ {header: chainA[3]}, From 78f5b389552655058b9ab0e60b4a4982c906d193 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Thu, 19 May 2022 09:41:35 -0400 Subject: [PATCH 47/93] chore: update comment --- dot/digest/digest.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dot/digest/digest.go b/dot/digest/digest.go index 6d17ea22fc..3f37c8aeb4 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -88,7 +88,7 @@ func (h *Handler) HandleDigests(header *types.Header) { for i := range consensusDigests { // avoiding implicit memory aliasing in for loop, since: // for _, digest := range consensusDigests { &digest } - // I'm using the address of a loop variable + // is using the address of a loop variable digest := consensusDigests[i] err := h.handleConsensusDigest(&digest, header) if err != nil { From 4114c736460a43e924808621a70763ebde157ad0 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Thu, 19 May 2022 09:43:27 -0400 Subject: [PATCH 48/93] chore: include `BestFinalizedBlock` description --- dot/types/consensus_digest.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dot/types/consensus_digest.go b/dot/types/consensus_digest.go index 9cf8e59a96..b33e884555 100644 --- a/dot/types/consensus_digest.go +++ b/dot/types/consensus_digest.go @@ -31,6 +31,9 @@ func (sc GrandpaScheduledChange) Index() uint { return 1 } // GrandpaForcedChange represents a GRANDPA forced authority change type GrandpaForcedChange struct { + // BestFinalizedBlock is specified by the governance mechanism, defines + // the starting block at which Delay is applied. + // https://github.com/w3f/polkadot-spec/pull/506#issuecomment-1128849492 BestFinalizedBlock uint32 Auths []GrandpaAuthoritiesRaw Delay uint32 From 1f502b5a52091df8d96371aea0e85ded4fab2d44 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Mon, 23 May 2022 10:06:20 -0400 Subject: [PATCH 49/93] chore: adding a warn log to pause/resume grandpa handler --- dot/state/grandpa.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 63771bf2a3..2ac76064b7 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -93,8 +93,10 @@ func (s *GrandpaState) HandleGRANDPADigest(header *types.Header, digest scale.Va case types.GrandpaOnDisabled: return nil case types.GrandpaPause: + logger.Warn("GRANDPA Pause consensus message not imeplemented yet") return nil case types.GrandpaResume: + logger.Warn("GRANDPA Resume consensus message not imeplemented yet") return nil default: return fmt.Errorf("not supported digest") From 7b72104eeb0442a3ba99c9dfcb7de3b0c94d09b2 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Mon, 23 May 2022 10:21:16 -0400 Subject: [PATCH 50/93] chore: removing unneeded comments --- dot/digest/digest.go | 6 +++--- dot/digest/digest_test.go | 2 +- dot/state/grandpa.go | 2 +- dot/state/grandpa_test.go | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dot/digest/digest.go b/dot/digest/digest.go index 3f37c8aeb4..bd4c786d1b 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -175,7 +175,7 @@ func (h *Handler) handleConsensusDigest(d *types.ConsensusDigest, header *types. return h.handleBabeConsensusDigest(data, header) default: - return fmt.Errorf("%w: %s", ErrUnknownConsensusID, d.ConsensusEngineID.ToBytes()) + return fmt.Errorf("%w: 0x%x", ErrUnknownConsensusID, d.ConsensusEngineID.ToBytes()) } } @@ -225,7 +225,7 @@ func (h *Handler) handleBlockImport(ctx context.Context) { h.HandleDigests(&block.Header) err := h.grandpaState.ApplyForcedChanges(&block.Header) if err != nil { - h.logger.Errorf("cannot apply forced changes: %w", err) + h.logger.Errorf("failed to apply forced changes: %s", err) } case <-ctx.Done(): return @@ -253,7 +253,7 @@ func (h *Handler) handleBlockFinalisation(ctx context.Context) { err = h.grandpaState.ApplyScheduledChanges(&info.Header) if err != nil { - h.logger.Errorf("failed to apply scheduled change on block finalisation: %w", err) + h.logger.Errorf("failed to apply scheduled change: %s", err) } case <-ctx.Done(): diff --git a/dot/digest/digest_test.go b/dot/digest/digest_test.go index e7ccc16ab7..264fb3132d 100644 --- a/dot/digest/digest_test.go +++ b/dot/digest/digest_test.go @@ -598,7 +598,7 @@ func issueBlocksWithGRANDPAScheduledChanges(t *testing.T, kp *sr25519.Keypair, d sc types.GrandpaScheduledChange, atBlock int, size int) (headers []*types.Header) { t.Helper() - transcript := merlin.NewTranscript("BABE") //string(types.BabeEngineID[:]) + transcript := merlin.NewTranscript("BABE") crypto.AppendUint64(transcript, []byte("slot number"), 1) crypto.AppendUint64(transcript, []byte("current epoch"), 1) transcript.AppendMessage([]byte("chain randomness"), []byte{}) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 2ac76064b7..7039b13c02 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -350,7 +350,7 @@ func (s *GrandpaState) ApplyForcedChanges(importedBlockHeader *types.Header) err announcingHash := forced.announcingHeader.Hash() effectiveNumber := forced.effectiveNumber() - if importedBlockHash.Equal(announcingHash) && effectiveNumber == 0 { + if importedBlockHash.Equal(announcingHash) && effectiveNumber == importedBlockHeader.Number { forcedChange = forced break } diff --git a/dot/state/grandpa_test.go b/dot/state/grandpa_test.go index 715487dbd7..f61400b071 100644 --- a/dot/state/grandpa_test.go +++ b/dot/state/grandpa_test.go @@ -388,7 +388,7 @@ func issueBlocksWithBABEPrimary(t *testing.T, kp *sr25519.Keypair, bs *BlockState, parentHeader *types.Header, size int) (headers []*types.Header) { t.Helper() - transcript := merlin.NewTranscript("BABE") //string(types.BabeEngineID[:]) + transcript := merlin.NewTranscript("BABE") crypto.AppendUint64(transcript, []byte("slot number"), 1) crypto.AppendUint64(transcript, []byte("current epoch"), 1) transcript.AppendMessage([]byte("chain randomness"), []byte{}) From 82fa53ef0d9e7d42bf65524ddb9e6128938534f5 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Mon, 23 May 2022 10:24:17 -0400 Subject: [PATCH 51/93] chore: update recursive block creator helper func --- dot/digest/digest_test.go | 10 +++++----- dot/digest/interface.go | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dot/digest/digest_test.go b/dot/digest/digest_test.go index 264fb3132d..fb2f0226ed 100644 --- a/dot/digest/digest_test.go +++ b/dot/digest/digest_test.go @@ -651,12 +651,12 @@ func issueBlocksWithGRANDPAScheduledChanges(t *testing.T, kp *sr25519.Keypair, d dh.HandleDigests(header) - if size <= 0 { - headers = append(headers, header) - return headers + headers = append(headers, header) + + if size > 0 { + nestedHeaders := issueBlocksWithGRANDPAScheduledChanges(t, kp, dh, stateSvc, header, sc, atBlock, size-1) + headers = append(headers, nestedHeaders...) } - headers = append(headers, header) - headers = append(headers, issueBlocksWithGRANDPAScheduledChanges(t, kp, dh, stateSvc, header, sc, atBlock, size-1)...) return headers } diff --git a/dot/digest/interface.go b/dot/digest/interface.go index 4252884915..2913697d0f 100644 --- a/dot/digest/interface.go +++ b/dot/digest/interface.go @@ -42,6 +42,6 @@ type GrandpaState interface { GetCurrentSetID() (uint64, error) HandleGRANDPADigest(header *types.Header, digest scale.VaryingDataType) error - ApplyScheduledChanges(finalizedheader *types.Header) error + ApplyScheduledChanges(finalizedHeader *types.Header) error ApplyForcedChanges(importedHeader *types.Header) error } From 3b045133e8ad706856c540edaa96411fc6c9a807 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Mon, 23 May 2022 10:42:39 -0400 Subject: [PATCH 52/93] chore: removing `appliedBefore` change method --- dot/state/grandpa.go | 42 +++++++++++++++++++++--------------- dot/state/grandpa_changes.go | 12 ----------- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 7039b13c02..6fa93e4340 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -433,13 +433,13 @@ func (s *GrandpaState) ApplyForcedChanges(importedBlockHeader *types.Header) err // forcedChangeOnChainOf walk through the forced change slice looking for // a forced change that belong to the same branch as blockHash parameter -func (s *GrandpaState) forcedChangeOnChainOf(blockHash common.Hash) (*pendingChange, error) { - for _, forcedChange := range s.forcedChanges { - forcedChangeHeader := forcedChange.announcingHeader +func (s *GrandpaState) forcedChangeOnChainOf(blockHash common.Hash) (forcedChange *pendingChange, err error) { + for _, change := range s.forcedChanges { + changeHeader := change.announcingHeader var isDescendant bool - isDescendant, err := s.blockState.IsDescendantOf( - forcedChangeHeader.Hash(), blockHash) + isDescendant, err = s.blockState.IsDescendantOf( + changeHeader.Hash(), blockHash) if err != nil { return nil, fmt.Errorf("cannot verify ancestry: %w", err) @@ -449,18 +449,18 @@ func (s *GrandpaState) forcedChangeOnChainOf(blockHash common.Hash) (*pendingCha continue } - return forcedChange, nil + return change, nil } - return nil, nil //nolint:nilnil + return forcedChange, nil } // scheduledChangeOnChainOf walk only through the scheduled changes roots slice looking for // a scheduled change that belongs to the same branch as blockHash parameter -func (s *GrandpaState) scheduledChangeOnChainOf(blockHash common.Hash) (*pendingChange, error) { - for _, scheduledChange := range s.scheduledChangeRoots { +func (s *GrandpaState) scheduledChangeOnChainOf(blockHash common.Hash) (scheduledChange *pendingChange, err error) { + for _, change := range s.scheduledChangeRoots { isDescendant, err := s.blockState.IsDescendantOf( - scheduledChange.change.announcingHeader.Hash(), blockHash) + change.change.announcingHeader.Hash(), blockHash) if err != nil { return nil, fmt.Errorf("cannot verify ancestry: %w", err) @@ -470,10 +470,10 @@ func (s *GrandpaState) scheduledChangeOnChainOf(blockHash common.Hash) (*pending continue } - return scheduledChange.change, nil + return change.change, nil } - return nil, nil //nolint:nilnil + return scheduledChange, nil } // NextGrandpaAuthorityChange returns the block number of the next upcoming grandpa authorities change. @@ -491,13 +491,21 @@ func (s *GrandpaState) NextGrandpaAuthorityChange(bestBlockHash common.Hash) (bl bestBlockHash, err) } - if forcedChange.appliedBefore(scheduledChange) { - return forcedChange.effectiveNumber(), nil - } else if scheduledChange != nil { - return scheduledChange.effectiveNumber(), nil + var next uint + + if scheduledChange != nil { + next = scheduledChange.effectiveNumber() + } + + if forcedChange != nil && forcedChange.effectiveNumber() < next { + next = forcedChange.effectiveNumber() + } + + if next == 0 { + return 0, ErrNoChanges } - return 0, ErrNoChanges + return next, nil } func authoritiesKey(setID uint64) []byte { diff --git a/dot/state/grandpa_changes.go b/dot/state/grandpa_changes.go index 79cda030d1..41f46978d6 100644 --- a/dot/state/grandpa_changes.go +++ b/dot/state/grandpa_changes.go @@ -28,18 +28,6 @@ func (p *pendingChange) effectiveNumber() uint { return p.announcingHeader.Number + uint(p.delay) } -// appliedBefore compares the effective number between two pending changes -// and returns true if: -// - the current pending change is applied before the target -// - the target is nil and the current contains a value -func (p *pendingChange) appliedBefore(target *pendingChange) bool { - if p != nil && target != nil { - return p.effectiveNumber() < target.effectiveNumber() - } - - return p != nil -} - type orderedPendingChanges []*pendingChange func (o orderedPendingChanges) Len() int { return len(o) } From f57e49db53330ee5b9befb1755dcaa1a1ba73399 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Mon, 23 May 2022 11:15:24 -0400 Subject: [PATCH 53/93] chore: unneeded block number comparision while pruning forced changes --- dot/state/grandpa.go | 21 +++++++++++---------- dot/state/grandpa_test.go | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 6fa93e4340..fe296a87e9 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -235,7 +235,7 @@ func (s *GrandpaState) getApplicableScheduledChange(finalizedHash common.Hash, f // otherwise we should update the scheduled roots to be the child // nodes of the applied scheduled change if changeNode == nil { - err := s.keepDescendantScheduledChanges(finalizedHash) + err := s.pruneScheduledChanges(finalizedHash) if err != nil { return nil, fmt.Errorf("cannot keep descendant scheduled nodes: %w", err) } @@ -248,10 +248,10 @@ func (s *GrandpaState) getApplicableScheduledChange(finalizedHash common.Hash, f return change, nil } -// keepDescendantForcedChanges should keep the forced changes for later blocks that -// are descendant of the finalized block -func (s *GrandpaState) keepDescendantForcedChanges(finalizedHash common.Hash, finalizedNumber uint) error { - onBranchForcedChanges := []*pendingChange{} +// pruneForcedChanges removes the forced changes from GRANDPA state for any block +// that is not a descendant of the current finalised block. +func (s *GrandpaState) pruneForcedChanges(finalizedHash common.Hash) error { + onBranchForcedChanges := make([]*pendingChange, 0, len(s.forcedChanges)) for _, forcedChange := range s.forcedChanges { isDescendant, err := s.blockState.IsDescendantOf(finalizedHash, forcedChange.announcingHeader.Hash()) @@ -259,7 +259,7 @@ func (s *GrandpaState) keepDescendantForcedChanges(finalizedHash common.Hash, fi return fmt.Errorf("cannot verify ancestry: %w", err) } - if forcedChange.effectiveNumber() > finalizedNumber && isDescendant { + if isDescendant { onBranchForcedChanges = append(onBranchForcedChanges, forcedChange) } } @@ -270,9 +270,10 @@ func (s *GrandpaState) keepDescendantForcedChanges(finalizedHash common.Hash, fi return nil } -// keepDescendantScheduledChanges keeps the not applied scheduled changes and remove purged scheduled changes -func (s *GrandpaState) keepDescendantScheduledChanges(finalizedHash common.Hash) error { - onBranchScheduledChanges := []*pendingChangeNode{} +// pruneScheduledChanges removes the scheduled changes from the +// GRANDPA state which are not for a descendant of the finalised block hash. +func (s *GrandpaState) pruneScheduledChanges(finalizedHash common.Hash) error { + onBranchScheduledChanges := make([]*pendingChangeNode, 0, len(s.scheduledChangeRoots)) for _, scheduledChange := range s.scheduledChangeRoots { scheduledChangeHash := scheduledChange.change.announcingHeader.Hash() @@ -297,7 +298,7 @@ func (s *GrandpaState) keepDescendantScheduledChanges(finalizedHash common.Hash) func (s *GrandpaState) ApplyScheduledChanges(finalizedHeader *types.Header) error { finalizedHash := finalizedHeader.Hash() - err := s.keepDescendantForcedChanges(finalizedHash, finalizedHeader.Number) + err := s.pruneForcedChanges(finalizedHash) if err != nil { return fmt.Errorf("cannot keep descendant forced changes: %w", err) } diff --git a/dot/state/grandpa_test.go b/dot/state/grandpa_test.go index f61400b071..750183c37d 100644 --- a/dot/state/grandpa_test.go +++ b/dot/state/grandpa_test.go @@ -859,7 +859,7 @@ func TestApplyScheduledChangesKeepDescendantForcedChanges(t *testing.T) { selectedFork := forks[tt.finalizedHeader[0]] selectedFinalizedHeader := selectedFork[tt.finalizedHeader[1]] - err = gs.keepDescendantForcedChanges(selectedFinalizedHeader.Hash(), selectedFinalizedHeader.Number) + err = gs.pruneForcedChanges(selectedFinalizedHeader.Hash()) if tt.wantErr != nil { require.EqualError(t, err, tt.wantErr.Error()) } else { From 2ee19672da76e94ed736962eb9a558864b755948 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Mon, 23 May 2022 11:17:43 -0400 Subject: [PATCH 54/93] chore: fix the logs and errors fmt --- dot/state/grandpa.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index fe296a87e9..c3686658bf 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -237,7 +237,7 @@ func (s *GrandpaState) getApplicableScheduledChange(finalizedHash common.Hash, f if changeNode == nil { err := s.pruneScheduledChanges(finalizedHash) if err != nil { - return nil, fmt.Errorf("cannot keep descendant scheduled nodes: %w", err) + return nil, fmt.Errorf("cannot prune non-descendant scheduled nodes: %w", err) } } else { change = changeNode.change @@ -300,7 +300,7 @@ func (s *GrandpaState) ApplyScheduledChanges(finalizedHeader *types.Header) erro err := s.pruneForcedChanges(finalizedHash) if err != nil { - return fmt.Errorf("cannot keep descendant forced changes: %w", err) + return fmt.Errorf("cannot prune non-descendant forced changes: %w", err) } if len(s.scheduledChangeRoots) == 0 { @@ -309,7 +309,7 @@ func (s *GrandpaState) ApplyScheduledChanges(finalizedHeader *types.Header) erro changeToApply, err := s.getApplicableScheduledChange(finalizedHash, finalizedHeader.Number) if err != nil { - return fmt.Errorf("cannot finalize scheduled change: %w", err) + return fmt.Errorf("cannot get applicable scheduled change: %w", err) } if changeToApply == nil { @@ -331,7 +331,7 @@ func (s *GrandpaState) ApplyScheduledChanges(finalizedHeader *types.Header) erro err = s.setChangeSetIDAtBlock(newSetID, changeToApply.effectiveNumber()) if err != nil { - return fmt.Errorf("cannot set change set id at block") + return fmt.Errorf("cannot set the change set id at block: %w", err) } logger.Debugf("Applying authority set change scheduled at block #%d", From c46fa528b63dd7dbf70e3bd2b2c6c770364fae38 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Mon, 23 May 2022 11:20:13 -0400 Subject: [PATCH 55/93] chore: encode consensus engine ID as hexadecimal --- dot/state/grandpa.go | 2 +- dot/types/digest.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index c3686658bf..2da2829d9a 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -294,7 +294,7 @@ func (s *GrandpaState) pruneScheduledChanges(finalizedHash common.Hash) error { } // ApplyScheduledChanges will check the schedules changes in order to find a root -// that is equals or behind the finalized number and will apply its authority set changes +// equal or behind the finalized number and will apply its authority set changes func (s *GrandpaState) ApplyScheduledChanges(finalizedHeader *types.Header) error { finalizedHash := finalizedHeader.Hash() diff --git a/dot/types/digest.go b/dot/types/digest.go index 12d30e3296..4624d25837 100644 --- a/dot/types/digest.go +++ b/dot/types/digest.go @@ -30,7 +30,7 @@ func (h ConsensusEngineID) ToBytes() []byte { } func (h ConsensusEngineID) String() string { - return string(h.ToBytes()) + return fmt.Sprintf("0x%x", h.ToBytes()) } // BabeEngineID is the hard-coded babe ID From 40b42941a9f64f76834a51133160d7ee9f7475b6 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Mon, 23 May 2022 11:23:04 -0400 Subject: [PATCH 56/93] chore: using `handleConsensusDigest` err context --- dot/digest/digest.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dot/digest/digest.go b/dot/digest/digest.go index bd4c786d1b..21f0354f6c 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -81,7 +81,7 @@ func (h *Handler) HandleDigests(header *types.Header) { consensusDigests := ToConsensusDigests(header.Digest.Types) consensusDigests, err := checkForGRANDPAForcedChanges(consensusDigests) if err != nil { - h.logger.Errorf("cannot ignore multiple GRANDPA digests: %w", err) + h.logger.Errorf("cannot ignore multiple GRANDPA digests: %s", err) return } @@ -92,8 +92,7 @@ func (h *Handler) HandleDigests(header *types.Header) { digest := consensusDigests[i] err := h.handleConsensusDigest(&digest, header) if err != nil { - h.logger.Errorf("cannot handle digest for block number %d, index %d, digest %s: %s", - header.Number, i, digest, err) + h.logger.Errorf("cannot handle consensus digest: %w", err) } } } From 8d1d17eba7643a55ea3533be6663f0a5d0adbdab Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Mon, 23 May 2022 11:24:08 -0400 Subject: [PATCH 57/93] chore: improving comment on `ToConsensusDigests` --- dot/digest/digest.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dot/digest/digest.go b/dot/digest/digest.go index 21f0354f6c..f38f78bd5b 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -97,7 +97,7 @@ func (h *Handler) HandleDigests(header *types.Header) { } } -// ToConsensusDigests will parse an []scale.VaryingDataType slice into []types.ConsensusDigest +// ToConsensusDigests converts a slice of scale.VaryingDataType to a slice of types.ConsensusDigest. func ToConsensusDigests(scaleVaryingTypes []scale.VaryingDataType) []types.ConsensusDigest { consensusDigests := make([]types.ConsensusDigest, 0, len(scaleVaryingTypes)) From 5989e300b400c65e2c1d2e5e5ed10509e3af62e5 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Mon, 23 May 2022 11:26:45 -0400 Subject: [PATCH 58/93] chore: use a log to warn when we dont support a digest type --- dot/digest/digest.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dot/digest/digest.go b/dot/digest/digest.go index f38f78bd5b..59a93cda59 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -78,7 +78,7 @@ func (h *Handler) Stop() error { // HandleDigests handles consensus digests for an imported block func (h *Handler) HandleDigests(header *types.Header) { - consensusDigests := ToConsensusDigests(header.Digest.Types) + consensusDigests := h.toConsensusDigests(header.Digest.Types) consensusDigests, err := checkForGRANDPAForcedChanges(consensusDigests) if err != nil { h.logger.Errorf("cannot ignore multiple GRANDPA digests: %s", err) @@ -97,13 +97,14 @@ func (h *Handler) HandleDigests(header *types.Header) { } } -// ToConsensusDigests converts a slice of scale.VaryingDataType to a slice of types.ConsensusDigest. -func ToConsensusDigests(scaleVaryingTypes []scale.VaryingDataType) []types.ConsensusDigest { +// toConsensusDigests converts a slice of scale.VaryingDataType to a slice of types.ConsensusDigest. +func (h *Handler) toConsensusDigests(scaleVaryingTypes []scale.VaryingDataType) []types.ConsensusDigest { consensusDigests := make([]types.ConsensusDigest, 0, len(scaleVaryingTypes)) for _, d := range scaleVaryingTypes { digest, ok := d.Value().(types.ConsensusDigest) if !ok { + h.logger.Debugf("digest type not supported: %T", d.Value()) continue } From d6268f341da0a85970d24693183e959effff9804 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Mon, 23 May 2022 14:02:51 -0400 Subject: [PATCH 59/93] chore: add comment to `checkGRANDPAForcedChange` --- dot/digest/digest.go | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/dot/digest/digest.go b/dot/digest/digest.go index 59a93cda59..19b1e16cce 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -117,25 +117,28 @@ func (h *Handler) toConsensusDigests(scaleVaryingTypes []scale.VaryingDataType) return consensusDigests } +// checkForGRANDPAForcedChanges removes any GrandpaScheduledChange in the presence of a +// GrandpaForcedChange in the same block digest, returning a new slice of types.ConsensusDigest func checkForGRANDPAForcedChanges(digests []types.ConsensusDigest) ([]types.ConsensusDigest, error) { var hasForcedChange bool scheduledChangesIndex := make(map[int]struct{}, len(digests)) for idx, digest := range digests { - switch digest.ConsensusEngineID { - case types.GrandpaEngineID: - data := types.NewGrandpaConsensusDigest() - err := scale.Unmarshal(digest.Data, &data) - if err != nil { - return nil, fmt.Errorf("cannot unmarshal GRANDPA consensus digest: %w", err) - } + if digest.ConsensusEngineID != types.GrandpaEngineID { + continue + } - switch data.Value().(type) { - case types.GrandpaScheduledChange: - scheduledChangesIndex[idx] = struct{}{} - case types.GrandpaForcedChange: - hasForcedChange = true - } + data := types.NewGrandpaConsensusDigest() + err := scale.Unmarshal(digest.Data, &data) + if err != nil { + return nil, fmt.Errorf("cannot unmarshal GRANDPA consensus digest: %w", err) + } + + switch data.Value().(type) { + case types.GrandpaScheduledChange: + scheduledChangesIndex[idx] = struct{}{} + case types.GrandpaForcedChange: + hasForcedChange = true } } From 168957064b5d8307da1006be5472ccc887a74eae Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Mon, 23 May 2022 14:32:40 -0400 Subject: [PATCH 60/93] chore: dont using aditional loops --- dot/digest/digest.go | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/dot/digest/digest.go b/dot/digest/digest.go index 19b1e16cce..c6bb9cdf8a 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -121,9 +121,8 @@ func (h *Handler) toConsensusDigests(scaleVaryingTypes []scale.VaryingDataType) // GrandpaForcedChange in the same block digest, returning a new slice of types.ConsensusDigest func checkForGRANDPAForcedChanges(digests []types.ConsensusDigest) ([]types.ConsensusDigest, error) { var hasForcedChange bool - scheduledChangesIndex := make(map[int]struct{}, len(digests)) - - for idx, digest := range digests { + digestsWithoutScheduled := make([]types.ConsensusDigest, 0, len(digests)) + for _, digest := range digests { if digest.ConsensusEngineID != types.GrandpaEngineID { continue } @@ -136,23 +135,15 @@ func checkForGRANDPAForcedChanges(digests []types.ConsensusDigest) ([]types.Cons switch data.Value().(type) { case types.GrandpaScheduledChange: - scheduledChangesIndex[idx] = struct{}{} case types.GrandpaForcedChange: hasForcedChange = true + digestsWithoutScheduled = append(digestsWithoutScheduled, digest) + default: + digestsWithoutScheduled = append(digestsWithoutScheduled, digest) } } if hasForcedChange { - digestsWithoutScheduled := make([]types.ConsensusDigest, 0, len(digests)-len(scheduledChangesIndex)) - for idx, digest := range digests { - _, ok := scheduledChangesIndex[idx] - if ok { - continue - } - - digestsWithoutScheduled = append(digestsWithoutScheduled, digest) - } - return digestsWithoutScheduled, nil } From b597b1d15fb84a7953ec8be321a5e8b9bb05511e Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Mon, 23 May 2022 15:09:00 -0400 Subject: [PATCH 61/93] chore: fix the lint --- dot/state/grandpa.go | 1 - 1 file changed, 1 deletion(-) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 2da2829d9a..c02128a426 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -21,7 +21,6 @@ var ( errDuplicateHashes = errors.New("duplicated hashes") errAlreadyHasForcedChanges = errors.New("already has a forced change") errUnfinalizedAncestor = errors.New("ancestor with changes not applied") - errLowerThanBestFinalized = errors.New("current finalized is lower than best finalized header") ErrNoChanges = errors.New("cannot get the next authority change block number") ) From 261db6dceb7f48fa635ba844b5594ca49fce61c0 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Mon, 23 May 2022 15:43:26 -0400 Subject: [PATCH 62/93] wip: working on digests tests --- dot/digest/digest_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dot/digest/digest_test.go b/dot/digest/digest_test.go index fb2f0226ed..a2ad0bcc1d 100644 --- a/dot/digest/digest_test.go +++ b/dot/digest/digest_test.go @@ -521,8 +521,6 @@ func TestGrandpaScheduledChanges(t *testing.T) { } } - digestHandler, stateService := newTestHandler(t) - forksGRANPDAScheduledChanges := []types.GrandpaScheduledChange{ { Auths: []types.GrandpaAuthoritiesRaw{ @@ -565,6 +563,8 @@ func TestGrandpaScheduledChanges(t *testing.T) { }, } + digestHandler, stateService := newTestHandler(t) + genesisHeader, err := stateService.Block.BestBlockHeader() require.NoError(t, err) From 977ee71a311cb1f81c8247ffb045d2972845ca53 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Tue, 31 May 2022 17:43:21 -0400 Subject: [PATCH 63/93] chore: make the change tracker more generic --- dot/state/grandpa.go | 312 ++++++----------------------------- dot/state/grandpa_changes.go | 248 +++++++++++++++++++++++++++- dot/state/grandpa_test.go | 39 +++-- 3 files changed, 312 insertions(+), 287 deletions(-) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index c02128a426..2c91cbdd28 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -7,7 +7,6 @@ import ( "encoding/binary" "errors" "fmt" - "sort" "sync" "github.com/ChainSafe/chaindb" @@ -42,8 +41,8 @@ type GrandpaState struct { forksLock sync.RWMutex - scheduledChangeRoots []*pendingChangeNode - forcedChanges orderedPendingChanges + forcedChanges changeTracker[*pendingChange, *pendingChange] + scheduledChangeRoots changeTracker[*pendingChange, *pendingChangeNode] } // NewGrandpaStateFromGenesis returns a new GrandpaState given the grandpa genesis authorities @@ -51,8 +50,10 @@ func NewGrandpaStateFromGenesis(db chaindb.Database, bs *BlockState, genesisAuthorities []types.GrandpaVoter) (*GrandpaState, error) { grandpaDB := chaindb.NewTable(db, grandpaPrefix) s := &GrandpaState{ - db: grandpaDB, - blockState: bs, + db: grandpaDB, + blockState: bs, + scheduledChangeRoots: new(changeTree), + forcedChanges: new(orderedPendingChanges), } if err := s.setCurrentSetID(genesisSetID); err != nil { @@ -77,8 +78,10 @@ func NewGrandpaStateFromGenesis(db chaindb.Database, bs *BlockState, // NewGrandpaState returns a new GrandpaState func NewGrandpaState(db chaindb.Database, bs *BlockState) (*GrandpaState, error) { return &GrandpaState{ - db: chaindb.NewTable(db, grandpaPrefix), - blockState: bs, + db: chaindb.NewTable(db, grandpaPrefix), + blockState: bs, + scheduledChangeRoots: new(changeTree), + forcedChanges: new(orderedPendingChanges), }, nil } @@ -103,25 +106,6 @@ func (s *GrandpaState) HandleGRANDPADigest(header *types.Header, digest scale.Va } func (s *GrandpaState) addForcedChange(header *types.Header, fc types.GrandpaForcedChange) error { - headerHash := header.Hash() - - for _, change := range s.forcedChanges { - changeBlockHash := change.announcingHeader.Hash() - - if changeBlockHash.Equal(headerHash) { - return errDuplicateHashes - } - - isDescendant, err := s.blockState.IsDescendantOf(changeBlockHash, headerHash) - if err != nil { - return fmt.Errorf("cannot verify ancestry: %w", err) - } - - if isDescendant { - return errAlreadyHasForcedChanges - } - } - auths, err := types.GrandpaAuthoritiesRawToAuthorities(fc.Auths) if err != nil { return fmt.Errorf("cannot parser GRANDPA authorities to raw authorities: %w", err) @@ -134,10 +118,12 @@ func (s *GrandpaState) addForcedChange(header *types.Header, fc types.GrandpaFor delay: fc.Delay, } - s.forcedChanges = append(s.forcedChanges, pendingChange) - sort.Sort(s.forcedChanges) + err = s.forcedChanges.importChange(pendingChange, s.blockState.IsDescendantOf) + if err != nil { + return fmt.Errorf("cannot import forced change: %w", err) + } - logger.Debugf("there are now %d possible forced changes", len(s.forcedChanges)) + logger.Debugf("there are now %d possible forced changes", s.forcedChanges.Len()) return nil } @@ -153,142 +139,12 @@ func (s *GrandpaState) addScheduledChange(header *types.Header, sc types.Grandpa delay: sc.Delay, } - return s.importScheduledChange(pendingChange) -} - -func (s *GrandpaState) importScheduledChange(pendingChange *pendingChange) error { - for _, root := range s.scheduledChangeRoots { - imported, err := root.importScheduledChange(pendingChange.announcingHeader.Hash(), - pendingChange.announcingHeader.Number, pendingChange, s.blockState.IsDescendantOf) - - if err != nil { - return fmt.Errorf("could not import scheduled change: %w", err) - } - - if imported { - logger.Debugf("changes on header %s (%d) imported successfully", - pendingChange.announcingHeader.Hash(), pendingChange.announcingHeader.Number) - return nil - } - } - - pendingChangeNode := &pendingChangeNode{ - change: pendingChange, - nodes: []*pendingChangeNode{}, - } - - s.scheduledChangeRoots = append(s.scheduledChangeRoots, pendingChangeNode) - logger.Debugf("there are now %d possible scheduled changes (roots)", len(s.scheduledChangeRoots)) - - return nil -} - -// getApplicableScheduledChange iterates through the scheduled change tree roots looking -// for the change node which contains a lower or equal effective number, to apply. -// When we found that node we update the scheduled change tree roots with its children -// that belongs to the same finalized node branch. If we don't find such node we update the scheduled -// change tree roots with the change nodes that belongs to the same finalized node branch -func (s *GrandpaState) getApplicableScheduledChange(finalizedHash common.Hash, finalizedNumber uint) ( - change *pendingChange, err error) { - - var changeNode *pendingChangeNode - for _, root := range s.scheduledChangeRoots { - if root.change.effectiveNumber() > finalizedNumber { - continue - } - - changeNodeHash := root.change.announcingHeader.Hash() - - // if the change doesn't have the same hash - // neither the finalized header is descendant of the change then skip to the next root - if !finalizedHash.Equal(changeNodeHash) { - isDescendant, err := s.blockState.IsDescendantOf(changeNodeHash, finalizedHash) - if err != nil { - return nil, fmt.Errorf("cannot verify ancestry: %w", err) - } - - if !isDescendant { - continue - } - } - - // the changes must be applied in order, so we need to check if our finalized header - // is ahead of any children, if it is that means some previous change was not applied - for _, child := range root.nodes { - isDescendant, err := s.blockState.IsDescendantOf(child.change.announcingHeader.Hash(), finalizedHash) - if err != nil { - return nil, fmt.Errorf("cannot verify ancestry: %w", err) - } - - if child.change.announcingHeader.Number <= finalizedNumber && isDescendant { - return nil, errUnfinalizedAncestor - } - } - - changeNode = root - break - } - - // if there is no change to be applied then we should keep only - // the scheduled changes which belongs to the finalized header - // otherwise we should update the scheduled roots to be the child - // nodes of the applied scheduled change - if changeNode == nil { - err := s.pruneScheduledChanges(finalizedHash) - if err != nil { - return nil, fmt.Errorf("cannot prune non-descendant scheduled nodes: %w", err) - } - } else { - change = changeNode.change - s.scheduledChangeRoots = make([]*pendingChangeNode, len(changeNode.nodes)) - copy(s.scheduledChangeRoots, changeNode.nodes) - } - - return change, nil -} - -// pruneForcedChanges removes the forced changes from GRANDPA state for any block -// that is not a descendant of the current finalised block. -func (s *GrandpaState) pruneForcedChanges(finalizedHash common.Hash) error { - onBranchForcedChanges := make([]*pendingChange, 0, len(s.forcedChanges)) - - for _, forcedChange := range s.forcedChanges { - isDescendant, err := s.blockState.IsDescendantOf(finalizedHash, forcedChange.announcingHeader.Hash()) - if err != nil { - return fmt.Errorf("cannot verify ancestry: %w", err) - } - - if isDescendant { - onBranchForcedChanges = append(onBranchForcedChanges, forcedChange) - } - } - - s.forcedChanges = make(orderedPendingChanges, len(onBranchForcedChanges)) - copy(s.forcedChanges, onBranchForcedChanges) - - return nil -} - -// pruneScheduledChanges removes the scheduled changes from the -// GRANDPA state which are not for a descendant of the finalised block hash. -func (s *GrandpaState) pruneScheduledChanges(finalizedHash common.Hash) error { - onBranchScheduledChanges := make([]*pendingChangeNode, 0, len(s.scheduledChangeRoots)) - - for _, scheduledChange := range s.scheduledChangeRoots { - scheduledChangeHash := scheduledChange.change.announcingHeader.Hash() - - isDescendant, err := s.blockState.IsDescendantOf(finalizedHash, scheduledChangeHash) - if err != nil { - return fmt.Errorf("cannot verify ancestry: %w", err) - } - - if isDescendant { - onBranchScheduledChanges = append(onBranchScheduledChanges, scheduledChange) - } + err = s.scheduledChangeRoots.importChange(pendingChange, s.blockState.IsDescendantOf) + if err != nil { + return fmt.Errorf("cannot import scheduled change: %w", err) } - s.scheduledChangeRoots = make([]*pendingChangeNode, len(onBranchScheduledChanges)) - copy(s.scheduledChangeRoots, onBranchScheduledChanges) + logger.Debugf("there are now %d possible scheduled change roots", s.scheduledChangeRoots.Len()) return nil } @@ -297,16 +153,17 @@ func (s *GrandpaState) pruneScheduledChanges(finalizedHash common.Hash) error { func (s *GrandpaState) ApplyScheduledChanges(finalizedHeader *types.Header) error { finalizedHash := finalizedHeader.Hash() - err := s.pruneForcedChanges(finalizedHash) + err := s.forcedChanges.pruneChanges(finalizedHash, s.blockState.IsDescendantOf) if err != nil { return fmt.Errorf("cannot prune non-descendant forced changes: %w", err) } - if len(s.scheduledChangeRoots) == 0 { + if s.scheduledChangeRoots.Len() == 0 { return nil } - changeToApply, err := s.getApplicableScheduledChange(finalizedHash, finalizedHeader.Number) + changeToApply, err := s.scheduledChangeRoots.findApplicable(finalizedHash, + finalizedHeader.Number, s.blockState.IsDescendantOf) if err != nil { return fmt.Errorf("cannot get applicable scheduled change: %w", err) } @@ -315,26 +172,26 @@ func (s *GrandpaState) ApplyScheduledChanges(finalizedHeader *types.Header) erro return nil } - logger.Debugf("applying scheduled change: %s", changeToApply) + logger.Debugf("applying scheduled change: %s", changeToApply.change) newSetID, err := s.IncrementSetID() if err != nil { return fmt.Errorf("cannot increment set id: %w", err) } - grandpaVotersAuthorities := types.NewGrandpaVotersFromAuthorities(changeToApply.nextAuthorities) + grandpaVotersAuthorities := types.NewGrandpaVotersFromAuthorities(changeToApply.change.nextAuthorities) err = s.setAuthorities(newSetID, grandpaVotersAuthorities) if err != nil { return fmt.Errorf("cannot set authorities: %w", err) } - err = s.setChangeSetIDAtBlock(newSetID, changeToApply.effectiveNumber()) + err = s.setChangeSetIDAtBlock(newSetID, changeToApply.change.effectiveNumber()) if err != nil { return fmt.Errorf("cannot set the change set id at block: %w", err) } logger.Debugf("Applying authority set change scheduled at block #%d", - changeToApply.announcingHeader.Number) + changeToApply.change.announcingHeader.Number) // TODO: add afg.applying_scheduled_authority_set_change telemetry info here return nil @@ -343,55 +200,29 @@ func (s *GrandpaState) ApplyScheduledChanges(finalizedHeader *types.Header) erro // ApplyForcedChanges will check for if there is a scheduled forced change relative to the // imported block and then apply it otherwise nothing happens func (s *GrandpaState) ApplyForcedChanges(importedBlockHeader *types.Header) error { - importedBlockHash := importedBlockHeader.Hash() - var forcedChange *pendingChange - - for _, forced := range s.forcedChanges { - announcingHash := forced.announcingHeader.Hash() - effectiveNumber := forced.effectiveNumber() - - if importedBlockHash.Equal(announcingHash) && effectiveNumber == importedBlockHeader.Number { - forcedChange = forced - break - } - - isDescendant, err := s.blockState.IsDescendantOf(announcingHash, importedBlockHash) - if err != nil { - return fmt.Errorf("cannot check ancestry: %w", err) - } - - if !isDescendant { - continue - } - - if effectiveNumber == importedBlockHeader.Number { - forcedChange = forced - break - } - } - - if forcedChange == nil { + forcedChange, err := s.forcedChanges.findApplicable(importedBlockHeader.Hash(), + importedBlockHeader.Number, s.blockState.IsDescendantOf) + if err != nil { + return fmt.Errorf("cannot find applicable forced change: %w", err) + } else if forcedChange == nil { return nil } forcedChangeHash := forcedChange.announcingHeader.Hash() bestFinalizedNumber := forcedChange.bestFinalizedNumber - // checking for dependant pending scheduled changes - for _, scheduled := range s.scheduledChangeRoots { - if scheduled.change.effectiveNumber() > uint(bestFinalizedNumber) { - continue - } - - scheduledBlockHash := scheduled.change.announcingHeader.Hash() - isDescendant, err := s.blockState.IsDescendantOf(scheduledBlockHash, forcedChangeHash) - if err != nil { - return fmt.Errorf("cannot check ancestry: %w", err) + dependant, err := s.scheduledChangeRoots.lookupChangeWhere(func(pcn *pendingChangeNode) (bool, error) { + if pcn.change.effectiveNumber() > uint(bestFinalizedNumber) { + return false, nil } - if isDescendant { - return errPendingScheduledChanges - } + scheduledBlockHash := pcn.change.announcingHeader.Hash() + return s.blockState.IsDescendantOf(scheduledBlockHash, forcedChangeHash) + }) + if err != nil { + return fmt.Errorf("cannot check pending changes while applying forced change: %w", err) + } else if dependant != nil { + return fmt.Errorf("%w: %s", errPendingScheduledChanges, dependant.change) } logger.Debugf("applying forced change: %s", forcedChange) @@ -431,70 +262,29 @@ func (s *GrandpaState) ApplyForcedChanges(importedBlockHeader *types.Header) err return nil } -// forcedChangeOnChainOf walk through the forced change slice looking for -// a forced change that belong to the same branch as blockHash parameter -func (s *GrandpaState) forcedChangeOnChainOf(blockHash common.Hash) (forcedChange *pendingChange, err error) { - for _, change := range s.forcedChanges { - changeHeader := change.announcingHeader - - var isDescendant bool - isDescendant, err = s.blockState.IsDescendantOf( - changeHeader.Hash(), blockHash) - - if err != nil { - return nil, fmt.Errorf("cannot verify ancestry: %w", err) - } - - if !isDescendant { - continue - } - - return change, nil - } - - return forcedChange, nil -} - -// scheduledChangeOnChainOf walk only through the scheduled changes roots slice looking for -// a scheduled change that belongs to the same branch as blockHash parameter -func (s *GrandpaState) scheduledChangeOnChainOf(blockHash common.Hash) (scheduledChange *pendingChange, err error) { - for _, change := range s.scheduledChangeRoots { - isDescendant, err := s.blockState.IsDescendantOf( - change.change.announcingHeader.Hash(), blockHash) - - if err != nil { - return nil, fmt.Errorf("cannot verify ancestry: %w", err) - } - - if !isDescendant { - continue - } - - return change.change, nil - } - - return scheduledChange, nil -} - // NextGrandpaAuthorityChange returns the block number of the next upcoming grandpa authorities change. // It returns 0 if no change is scheduled. func (s *GrandpaState) NextGrandpaAuthorityChange(bestBlockHash common.Hash) (blockNumber uint, err error) { - forcedChange, err := s.forcedChangeOnChainOf(bestBlockHash) + forcedChange, err := s.forcedChanges.lookupChangeWhere(func(pc *pendingChange) (bool, error) { + return s.blockState.IsDescendantOf(pc.announcingHeader.Hash(), bestBlockHash) + }) if err != nil { return 0, fmt.Errorf("cannot get forced change on chain of %s: %w", bestBlockHash, err) } - scheduledChange, err := s.scheduledChangeOnChainOf(bestBlockHash) + scheduledChangeNode, err := s.scheduledChangeRoots.lookupChangeWhere(func(pcn *pendingChangeNode) (bool, error) { + return s.blockState.IsDescendantOf(pcn.change.announcingHeader.Hash(), bestBlockHash) + }) if err != nil { - return 0, fmt.Errorf("cannot get scheduled change on chain of %s: %w", + return 0, fmt.Errorf("cannot get forced change on chain of %s: %w", bestBlockHash, err) } var next uint - if scheduledChange != nil { - next = scheduledChange.effectiveNumber() + if scheduledChangeNode != nil { + next = scheduledChangeNode.change.effectiveNumber() } if forcedChange != nil && forcedChange.effectiveNumber() < next { diff --git a/dot/state/grandpa_changes.go b/dot/state/grandpa_changes.go index 41f46978d6..93e32f6a42 100644 --- a/dot/state/grandpa_changes.go +++ b/dot/state/grandpa_changes.go @@ -5,13 +5,23 @@ package state import ( "fmt" + "sort" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" ) +type conditionFunc[T any] func(T) (bool, error) type isDescendantOfFunc func(parent, child common.Hash) (bool, error) +type changeTracker[T any, F any] interface { + Len() int + importChange(T, isDescendantOfFunc) error + lookupChangeWhere(conditionFunc[F]) (F, error) + pruneChanges(common.Hash, isDescendantOfFunc) error + findApplicable(common.Hash, uint, isDescendantOfFunc) (F, error) +} + type pendingChange struct { bestFinalizedNumber uint32 delay uint32 @@ -30,13 +40,96 @@ func (p *pendingChange) effectiveNumber() uint { type orderedPendingChanges []*pendingChange -func (o orderedPendingChanges) Len() int { return len(o) } -func (o orderedPendingChanges) Swap(i, j int) { o[i], o[j] = o[j], o[i] } - // Less order by effective number and then by block number -func (o orderedPendingChanges) Less(i, j int) bool { - return o[i].effectiveNumber() < o[j].effectiveNumber() && - o[i].announcingHeader.Number < o[j].announcingHeader.Number +func (oc orderedPendingChanges) Len() int { return len(oc) } +func (oc orderedPendingChanges) Swap(i, j int) { oc[i], oc[j] = oc[j], oc[i] } +func (oc orderedPendingChanges) Less(i, j int) bool { + return oc[i].effectiveNumber() < oc[j].effectiveNumber() && + oc[i].announcingHeader.Number < oc[j].announcingHeader.Number +} + +// findApplicable try to retrieve an applicable change from the slice of forced changes +func (oc orderedPendingChanges) findApplicable(importedHash common.Hash, importedNumber uint, + isDescendatOf isDescendantOfFunc) (*pendingChange, error) { + + return oc.lookupChangeWhere(func(forced *pendingChange) (bool, error) { + announcingHash := forced.announcingHeader.Hash() + effectiveNumber := forced.effectiveNumber() + + if importedHash.Equal(announcingHash) && effectiveNumber == importedNumber { + return true, nil + } + + isDescendant, err := isDescendatOf(announcingHash, importedHash) + if err != nil { + return false, fmt.Errorf("cannot check ancestry: %w", err) + } + + return isDescendant && effectiveNumber == importedNumber, nil + }) + +} + +func (oc orderedPendingChanges) lookupChangeWhere(condition conditionFunc[*pendingChange]) (pendingChange *pendingChange, err error) { + for _, change := range oc { + ok, err := condition(change) + if err != nil { + return nil, fmt.Errorf("failed while applying condition: %w", err) + } + + if ok { + return change, nil + } + } + + return nil, nil //nolint:nilnil +} + +func (oc orderedPendingChanges) importChange(pendingChange *pendingChange, isDescendantOf isDescendantOfFunc) error { + announcingHeader := pendingChange.announcingHeader.Hash() + + for _, change := range oc { + changeBlockHash := change.announcingHeader.Hash() + + if changeBlockHash.Equal(announcingHeader) { + return errDuplicateHashes + } + + isDescendant, err := isDescendantOf(changeBlockHash, announcingHeader) + if err != nil { + return fmt.Errorf("cannot verify ancestry: %w", err) + } + + if isDescendant { + return errAlreadyHasForcedChanges + } + } + + oc = append(oc, pendingChange) + sort.Sort(oc) + + return nil +} + +// pruneChanges will remove changes whose are not descendant of the hash argument +// this function updates the current state of the change tree +func (oc *orderedPendingChanges) pruneChanges(hash common.Hash, isDescendantOf isDescendantOfFunc) error { + onBranchForcedChanges := make([]*pendingChange, 0, len(*oc)) + + for _, forcedChange := range *oc { + isDescendant, err := isDescendantOf(hash, forcedChange.announcingHeader.Hash()) + if err != nil { + return fmt.Errorf("cannot verify ancestry: %w", err) + } + + if isDescendant { + onBranchForcedChanges = append(onBranchForcedChanges, forcedChange) + } + } + + *oc = make(orderedPendingChanges, len(onBranchForcedChanges)) + copy(*oc, onBranchForcedChanges) + return nil } type pendingChangeNode struct { @@ -44,7 +137,7 @@ type pendingChangeNode struct { nodes []*pendingChangeNode } -func (c *pendingChangeNode) importScheduledChange(blockHash common.Hash, blockNumber uint, pendingChange *pendingChange, +func (c *pendingChangeNode) importNode(blockHash common.Hash, blockNumber uint, pendingChange *pendingChange, isDescendantOf isDescendantOfFunc) (imported bool, err error) { announcingHash := c.change.announcingHeader.Hash() @@ -57,7 +150,7 @@ func (c *pendingChangeNode) importScheduledChange(blockHash common.Hash, blockNu } for _, childrenNodes := range c.nodes { - imported, err := childrenNodes.importScheduledChange(blockHash, blockNumber, pendingChange, isDescendantOf) + imported, err := childrenNodes.importNode(blockHash, blockNumber, pendingChange, isDescendantOf) if err != nil { return false, err } @@ -80,3 +173,142 @@ func (c *pendingChangeNode) importScheduledChange(blockHash common.Hash, blockNu c.nodes = append(c.nodes, pendingChangeNode) return true, nil } + +// changeTree keeps track of the changes per fork allowing +// n forks in the same structure, this structure is intended +// to be an acyclic directed graph where the change nodes are +// placed by descendency order and number, you can ensure an +// node ancestry using the `isDescendantOfFunc` +type changeTree []*pendingChangeNode + +func (ct changeTree) Len() int { return len(ct) } +func (ct changeTree) importChange(pendingChange *pendingChange, isDescendantOf isDescendantOfFunc) error { + for _, root := range ct { + imported, err := root.importNode(pendingChange.announcingHeader.Hash(), + pendingChange.announcingHeader.Number, pendingChange, isDescendantOf) + + if err != nil { + return fmt.Errorf("could not import scheduled change: %w", err) + } + + if imported { + logger.Debugf("changes on header %s (%d) imported successfully", + pendingChange.announcingHeader.Hash(), pendingChange.announcingHeader.Number) + return nil + } + } + + pendingChangeNode := &pendingChangeNode{ + change: pendingChange, + nodes: []*pendingChangeNode{}, + } + + ct = append(ct, pendingChangeNode) + return nil +} + +// lookupChangesWhere returns the first change which satisfy the +// condition whithout modify the current state of the change tree +func (ct changeTree) lookupChangeWhere(condition conditionFunc[*pendingChangeNode]) (changeNode *pendingChangeNode, err error) { + for _, root := range ct { + ok, err := condition(root) + if err != nil { + return nil, fmt.Errorf("failed while applying condition: %w", err) + } + + if ok { + return root, nil + } + } + + return nil, nil //nolint:nilnil +} + +// findApplicable try to retrieve an applicable change +// from the tree, if it finds a change node then it will update the +// tree roots with the change node's children otherwise it will +// prune nodes that does not belongs to the same chain as `hash` argument +func (ct *changeTree) findApplicable(hash common.Hash, number uint, + isDescendantOf isDescendantOfFunc) (changeNode *pendingChangeNode, err error) { + + changeNode, err = ct.findApplicableChange(hash, number, isDescendantOf) + if err != nil { + return nil, fmt.Errorf("cannot find applicable change: %w", err) + } + + if changeNode == nil { + err := ct.pruneChanges(hash, isDescendantOf) + if err != nil { + return nil, fmt.Errorf("cannot prune changes: %w", err) + } + } else { + *ct = make([]*pendingChangeNode, len(changeNode.nodes)) + copy(*ct, changeNode.nodes) + } + + return changeNode, nil +} + +// findApplicableChange iterates through the change tree +// roots looking for the change node which: +// 1. contains the same hash as the one we're looking for. +// 2. contains a lower or equal effective number as the one we're looking for. +// 3. does not contains pending changes to be applied. +func (ct changeTree) findApplicableChange(hash common.Hash, number uint, + isDescendantOf isDescendantOfFunc) (changeNode *pendingChangeNode, err error) { + return ct.lookupChangeWhere(func(pcn *pendingChangeNode) (bool, error) { + if pcn.change.effectiveNumber() > number { + return false, nil + } + + changeNodeHash := pcn.change.announcingHeader.Hash() + if !hash.Equal(changeNodeHash) { + isDescendant, err := isDescendantOf(changeNodeHash, hash) + if err != nil { + return false, fmt.Errorf("cannot verify ancestry: %w", err) + } + + if !isDescendant { + return false, nil + } + } + + // the changes must be applied in order, so we need to check if our finalized header + // is ahead of any children, if it is that means some previous change was not applied + for _, child := range pcn.nodes { + isDescendant, err := isDescendantOf(child.change.announcingHeader.Hash(), hash) + if err != nil { + return false, fmt.Errorf("cannot verify ancestry: %w", err) + } + + if child.change.announcingHeader.Number <= number && isDescendant { + return false, errUnfinalizedAncestor + } + } + + return true, nil + }) +} + +// pruneChanges will remove changes whose are not descendant of the hash argument +// this function updates the current state of the change tree +func (ct changeTree) pruneChanges(hash common.Hash, isDescendantOf isDescendantOfFunc) error { + onBranchChanges := []*pendingChangeNode{} + + for _, root := range ct { + scheduledChangeHash := root.change.announcingHeader.Hash() + + isDescendant, err := isDescendantOf(hash, scheduledChangeHash) + if err != nil { + return fmt.Errorf("cannot verify ancestry: %w", err) + } + + if isDescendant { + onBranchChanges = append(onBranchChanges, root) + } + } + + ct = make([]*pendingChangeNode, len(onBranchChanges)) + copy(ct, onBranchChanges) + return nil +} diff --git a/dot/state/grandpa_test.go b/dot/state/grandpa_test.go index 750183c37d..8be720c756 100644 --- a/dot/state/grandpa_test.go +++ b/dot/state/grandpa_test.go @@ -219,7 +219,7 @@ func TestAddScheduledChangesKeepTheRightForkTree(t *testing.T) { // this does not cause race condition because t.Run without // t.Parallel() blocks until this function returns defer func() { - gs.scheduledChangeRoots = gs.scheduledChangeRoots[:0] + gs.scheduledChangeRoots = new(changeTree) }() updateHighestFinalizedHeaderOrDefault(t, gs.blockState, tt.highestFinalizedHeader, chainA[0]) @@ -238,7 +238,7 @@ func TestAddScheduledChangesKeepTheRightForkTree(t *testing.T) { require.Len(t, gs.scheduledChangeRoots, tt.expectedRoots) - for _, root := range gs.scheduledChangeRoots { + for _, root := range *gs.scheduledChangeRoots.(*changeTree) { parentHash := root.change.announcingHeader.Hash() assertDescendantChildren(t, parentHash, gs.blockState.IsDescendantOf, root.nodes) } @@ -247,10 +247,10 @@ func TestAddScheduledChangesKeepTheRightForkTree(t *testing.T) { } func assertDescendantChildren(t *testing.T, parentHash common.Hash, isDescendantOfFunc isDescendantOfFunc, - scheduledChanges []*pendingChangeNode) { + changes changeTree) { t.Helper() - for _, scheduled := range scheduledChanges { + for _, scheduled := range changes { scheduledChangeHash := scheduled.change.announcingHeader.Hash() isDescendant, err := isDescendantOfFunc(parentHash, scheduledChangeHash) require.NoError(t, err) @@ -328,9 +328,10 @@ func TestForcedScheduledChangesOrder(t *testing.T) { require.NoError(t, err, "failed to add forced change") } - for idx := 0; idx < len(gs.forcedChanges)-1; idx++ { - currentChange := gs.forcedChanges[idx] - nextChange := gs.forcedChanges[idx+1] + forcedChangesSlice := *gs.forcedChanges.(*orderedPendingChanges) + for idx := 0; idx < len(forcedChangesSlice)-1; idx++ { + currentChange := forcedChangesSlice[idx] + nextChange := forcedChangesSlice[idx+1] require.LessOrEqual(t, currentChange.effectiveNumber(), nextChange.effectiveNumber()) @@ -859,7 +860,7 @@ func TestApplyScheduledChangesKeepDescendantForcedChanges(t *testing.T) { selectedFork := forks[tt.finalizedHeader[0]] selectedFinalizedHeader := selectedFork[tt.finalizedHeader[1]] - err = gs.pruneForcedChanges(selectedFinalizedHeader.Hash()) + err = gs.forcedChanges.pruneChanges(selectedFinalizedHeader.Hash(), gs.blockState.IsDescendantOf) if tt.wantErr != nil { require.EqualError(t, err, tt.wantErr.Error()) } else { @@ -867,7 +868,8 @@ func TestApplyScheduledChangesKeepDescendantForcedChanges(t *testing.T) { require.Len(t, gs.forcedChanges, tt.expectedForcedChangesLen) - for _, forcedChange := range gs.forcedChanges { + forcedChangesSlice := *gs.forcedChanges.(*orderedPendingChanges) + for _, forcedChange := range forcedChangesSlice { isDescendant, err := gs.blockState.IsDescendantOf( selectedFinalizedHeader.Hash(), forcedChange.announcingHeader.Hash()) @@ -1085,13 +1087,13 @@ func TestApplyScheduledChangeGetApplicableChange(t *testing.T) { // saving the current state of scheduled changes to compare // with the next state in the case of an error (should keep the same) - previousScheduledChanges := make([]*pendingChangeNode, len(gs.scheduledChangeRoots)) - copy(previousScheduledChanges, gs.scheduledChangeRoots) + previousScheduledChanges := gs.scheduledChangeRoots.(*changeTree) selectedChain := forks[tt.finalizedHeader[0]] selectedHeader := selectedChain[tt.finalizedHeader[1]] - change, err := gs.getApplicableScheduledChange(selectedHeader.Hash(), selectedHeader.Number) + changeNode, err := gs.scheduledChangeRoots.findApplicable(selectedHeader.Hash(), + selectedHeader.Number, gs.blockState.IsDescendantOf) if tt.wantErr != nil { require.EqualError(t, err, tt.wantErr.Error()) require.Equal(t, previousScheduledChanges, gs.scheduledChangeRoots) @@ -1100,16 +1102,16 @@ func TestApplyScheduledChangeGetApplicableChange(t *testing.T) { if tt.expectedChange != nil { require.NoError(t, err) - require.Equal(t, tt.expectedChange.delay, change.delay) - require.Equal(t, tt.expectedChange.nextAuthorities, change.nextAuthorities) + require.Equal(t, tt.expectedChange.delay, changeNode.change.delay) + require.Equal(t, tt.expectedChange.nextAuthorities, changeNode.change.nextAuthorities) } else { - require.Nil(t, change) + require.Nil(t, changeNode) } require.Len(t, gs.scheduledChangeRoots, tt.expectedScheduledChangeRootsLen) // make sure all the next scheduled changes are descendant of the finalized hash assertDescendantChildren(t, - selectedHeader.Hash(), gs.blockState.IsDescendantOf, gs.scheduledChangeRoots) + selectedHeader.Hash(), gs.blockState.IsDescendantOf, *gs.scheduledChangeRoots.(*changeTree)) }) } } @@ -1322,7 +1324,8 @@ func TestApplyScheduledChange(t *testing.T) { // ensure the forced changes and scheduled changes // are descendant of the latest finalized header - for _, forcedChange := range gs.forcedChanges { + forcedChangeSlice := *gs.forcedChanges.(*orderedPendingChanges) + for _, forcedChange := range forcedChangeSlice { isDescendant, err := gs.blockState.IsDescendantOf( selectedFinalizedHeader.Hash(), forcedChange.announcingHeader.Hash()) @@ -1331,7 +1334,7 @@ func TestApplyScheduledChange(t *testing.T) { } assertDescendantChildren(t, - selectedFinalizedHeader.Hash(), gs.blockState.IsDescendantOf, gs.scheduledChangeRoots) + selectedFinalizedHeader.Hash(), gs.blockState.IsDescendantOf, *gs.scheduledChangeRoots.(*changeTree)) } require.Len(t, gs.forcedChanges, tt.expectedForcedChangesLen) From 6fb0d6922d759818719fdbfe36b329d2d1a6e5c9 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Tue, 31 May 2022 18:25:35 -0400 Subject: [PATCH 64/93] chore: not too generic --- dot/state/grandpa.go | 4 ++-- dot/state/grandpa_changes.go | 8 -------- dot/state/grandpa_test.go | 15 +++++++-------- 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 2c91cbdd28..513373b921 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -41,8 +41,8 @@ type GrandpaState struct { forksLock sync.RWMutex - forcedChanges changeTracker[*pendingChange, *pendingChange] - scheduledChangeRoots changeTracker[*pendingChange, *pendingChangeNode] + forcedChanges *orderedPendingChanges + scheduledChangeRoots *changeTree } // NewGrandpaStateFromGenesis returns a new GrandpaState given the grandpa genesis authorities diff --git a/dot/state/grandpa_changes.go b/dot/state/grandpa_changes.go index 93e32f6a42..504fc1948a 100644 --- a/dot/state/grandpa_changes.go +++ b/dot/state/grandpa_changes.go @@ -14,14 +14,6 @@ import ( type conditionFunc[T any] func(T) (bool, error) type isDescendantOfFunc func(parent, child common.Hash) (bool, error) -type changeTracker[T any, F any] interface { - Len() int - importChange(T, isDescendantOfFunc) error - lookupChangeWhere(conditionFunc[F]) (F, error) - pruneChanges(common.Hash, isDescendantOfFunc) error - findApplicable(common.Hash, uint, isDescendantOfFunc) (F, error) -} - type pendingChange struct { bestFinalizedNumber uint32 delay uint32 diff --git a/dot/state/grandpa_test.go b/dot/state/grandpa_test.go index 8be720c756..1321dc523f 100644 --- a/dot/state/grandpa_test.go +++ b/dot/state/grandpa_test.go @@ -238,7 +238,7 @@ func TestAddScheduledChangesKeepTheRightForkTree(t *testing.T) { require.Len(t, gs.scheduledChangeRoots, tt.expectedRoots) - for _, root := range *gs.scheduledChangeRoots.(*changeTree) { + for _, root := range *gs.scheduledChangeRoots { parentHash := root.change.announcingHeader.Hash() assertDescendantChildren(t, parentHash, gs.blockState.IsDescendantOf, root.nodes) } @@ -328,7 +328,7 @@ func TestForcedScheduledChangesOrder(t *testing.T) { require.NoError(t, err, "failed to add forced change") } - forcedChangesSlice := *gs.forcedChanges.(*orderedPendingChanges) + forcedChangesSlice := *gs.forcedChanges for idx := 0; idx < len(forcedChangesSlice)-1; idx++ { currentChange := forcedChangesSlice[idx] nextChange := forcedChangesSlice[idx+1] @@ -868,8 +868,7 @@ func TestApplyScheduledChangesKeepDescendantForcedChanges(t *testing.T) { require.Len(t, gs.forcedChanges, tt.expectedForcedChangesLen) - forcedChangesSlice := *gs.forcedChanges.(*orderedPendingChanges) - for _, forcedChange := range forcedChangesSlice { + for _, forcedChange := range *gs.forcedChanges { isDescendant, err := gs.blockState.IsDescendantOf( selectedFinalizedHeader.Hash(), forcedChange.announcingHeader.Hash()) @@ -1087,7 +1086,7 @@ func TestApplyScheduledChangeGetApplicableChange(t *testing.T) { // saving the current state of scheduled changes to compare // with the next state in the case of an error (should keep the same) - previousScheduledChanges := gs.scheduledChangeRoots.(*changeTree) + previousScheduledChanges := gs.scheduledChangeRoots selectedChain := forks[tt.finalizedHeader[0]] selectedHeader := selectedChain[tt.finalizedHeader[1]] @@ -1111,7 +1110,7 @@ func TestApplyScheduledChangeGetApplicableChange(t *testing.T) { require.Len(t, gs.scheduledChangeRoots, tt.expectedScheduledChangeRootsLen) // make sure all the next scheduled changes are descendant of the finalized hash assertDescendantChildren(t, - selectedHeader.Hash(), gs.blockState.IsDescendantOf, *gs.scheduledChangeRoots.(*changeTree)) + selectedHeader.Hash(), gs.blockState.IsDescendantOf, *gs.scheduledChangeRoots) }) } } @@ -1324,7 +1323,7 @@ func TestApplyScheduledChange(t *testing.T) { // ensure the forced changes and scheduled changes // are descendant of the latest finalized header - forcedChangeSlice := *gs.forcedChanges.(*orderedPendingChanges) + forcedChangeSlice := *gs.forcedChanges for _, forcedChange := range forcedChangeSlice { isDescendant, err := gs.blockState.IsDescendantOf( selectedFinalizedHeader.Hash(), forcedChange.announcingHeader.Hash()) @@ -1334,7 +1333,7 @@ func TestApplyScheduledChange(t *testing.T) { } assertDescendantChildren(t, - selectedFinalizedHeader.Hash(), gs.blockState.IsDescendantOf, *gs.scheduledChangeRoots.(*changeTree)) + selectedFinalizedHeader.Hash(), gs.blockState.IsDescendantOf, *gs.scheduledChangeRoots) } require.Len(t, gs.forcedChanges, tt.expectedForcedChangesLen) From 44ff71b519b7afc68f97b70ffe869e3b4afa77dc Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Wed, 1 Jun 2022 09:01:03 -0400 Subject: [PATCH 65/93] chore: fix lint warns --- dot/state/grandpa_changes.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/dot/state/grandpa_changes.go b/dot/state/grandpa_changes.go index 504fc1948a..0b95b33ff0 100644 --- a/dot/state/grandpa_changes.go +++ b/dot/state/grandpa_changes.go @@ -62,7 +62,9 @@ func (oc orderedPendingChanges) findApplicable(importedHash common.Hash, importe } -func (oc orderedPendingChanges) lookupChangeWhere(condition conditionFunc[*pendingChange]) (pendingChange *pendingChange, err error) { +// lookupChangeWhere return the first pending change which satisfy the condition +func (oc orderedPendingChanges) lookupChangeWhere(condition conditionFunc[*pendingChange]) ( + pendingChange *pendingChange, err error) { for _, change := range oc { ok, err := condition(change) if err != nil { @@ -77,6 +79,8 @@ func (oc orderedPendingChanges) lookupChangeWhere(condition conditionFunc[*pendi return nil, nil //nolint:nilnil } +// importChange only tracks the pending change if and only if it is the +// unique forced change in its fork, otherwise will return an error func (oc orderedPendingChanges) importChange(pendingChange *pendingChange, isDescendantOf isDescendantOfFunc) error { announcingHeader := pendingChange.announcingHeader.Hash() @@ -174,8 +178,8 @@ func (c *pendingChangeNode) importNode(blockHash common.Hash, blockNumber uint, type changeTree []*pendingChangeNode func (ct changeTree) Len() int { return len(ct) } -func (ct changeTree) importChange(pendingChange *pendingChange, isDescendantOf isDescendantOfFunc) error { - for _, root := range ct { +func (ct *changeTree) importChange(pendingChange *pendingChange, isDescendantOf isDescendantOfFunc) error { + for _, root := range *ct { imported, err := root.importNode(pendingChange.announcingHeader.Hash(), pendingChange.announcingHeader.Number, pendingChange, isDescendantOf) @@ -195,13 +199,14 @@ func (ct changeTree) importChange(pendingChange *pendingChange, isDescendantOf i nodes: []*pendingChangeNode{}, } - ct = append(ct, pendingChangeNode) + *ct = append(*ct, pendingChangeNode) return nil } // lookupChangesWhere returns the first change which satisfy the // condition whithout modify the current state of the change tree -func (ct changeTree) lookupChangeWhere(condition conditionFunc[*pendingChangeNode]) (changeNode *pendingChangeNode, err error) { +func (ct changeTree) lookupChangeWhere(condition conditionFunc[*pendingChangeNode]) ( + changeNode *pendingChangeNode, err error) { for _, root := range ct { ok, err := condition(root) if err != nil { From 7057d5e717668de62953f3ad4fe7ab3595a2d6f2 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Wed, 1 Jun 2022 11:15:34 -0400 Subject: [PATCH 66/93] chore: fixing tests --- dot/digest/digest_test.go | 46 ++++++++++++++++-------------------- dot/state/grandpa_changes.go | 6 ++--- 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/dot/digest/digest_test.go b/dot/digest/digest_test.go index a2ad0bcc1d..88593852a9 100644 --- a/dot/digest/digest_test.go +++ b/dot/digest/digest_test.go @@ -63,6 +63,13 @@ func TestHandler_GrandpaScheduledChange(t *testing.T) { handler.Start() defer handler.Stop() + // create 4 blocks and finalize only blocks 0, 1, 2 + headers, _ := state.AddBlocksToState(t, handler.blockState.(*state.BlockState), 4, false) + for i, h := range headers[:3] { + err := handler.blockState.(*state.BlockState).SetFinalisedHash(h.Hash(), uint64(i), 0) + require.NoError(t, err) + } + kr, err := keystore.NewEd25519Keyring() require.NoError(t, err) @@ -70,7 +77,7 @@ func TestHandler_GrandpaScheduledChange(t *testing.T) { Auths: []types.GrandpaAuthoritiesRaw{ {Key: kr.Alice().Public().(*ed25519.PublicKey).AsBytes(), ID: 0}, }, - Delay: 3, + Delay: 0, } var digest = types.NewGrandpaConsensusDigest() @@ -85,25 +92,13 @@ func TestHandler_GrandpaScheduledChange(t *testing.T) { Data: data, } - header := &types.Header{ - Number: 1, - } - - err = handler.handleConsensusDigest(d, header) + // include a GrandpaScheduledChange on a block of number 3 + err = handler.handleConsensusDigest(d, headers[3]) require.NoError(t, err) - headers, _ := state.AddBlocksToState(t, handler.blockState.(*state.BlockState), 2, false) - for i, h := range headers { - err = handler.blockState.(*state.BlockState).SetFinalisedHash(h.Hash(), uint64(i), 0) - require.NoError(t, err) - } - - // authorities should change on start of block 3 from start - headers, _ = state.AddBlocksToState(t, handler.blockState.(*state.BlockState), 1, false) - for _, h := range headers { - err = handler.blockState.(*state.BlockState).SetFinalisedHash(h.Hash(), 3, 0) - require.NoError(t, err) - } + // finalize block of number 3 + err = handler.blockState.(*state.BlockState).SetFinalisedHash(headers[3].Hash(), 3, 0) + require.NoError(t, err) time.Sleep(time.Millisecond * 500) setID, err := handler.grandpaState.(*state.GrandpaState).GetCurrentSetID() @@ -122,6 +117,9 @@ func TestHandler_GrandpaForcedChange(t *testing.T) { handler.Start() defer handler.Stop() + // authorities should change on start of block 4 from start + headers, _ := state.AddBlocksToState(t, handler.blockState.(*state.BlockState), 2, false) + kr, err := keystore.NewEd25519Keyring() require.NoError(t, err) @@ -144,17 +142,15 @@ func TestHandler_GrandpaForcedChange(t *testing.T) { Data: data, } - header := &types.Header{ - Number: 1, - } - - err = handler.handleConsensusDigest(d, header) + // tracking the GrandpaForcedChange under block 1 + // and when block number 4 being imported then we should apply the change + err = handler.handleConsensusDigest(d, headers[1]) require.NoError(t, err) - // authorities should change on start of block 4 from start + // create new blocks and import them state.AddBlocksToState(t, handler.blockState.(*state.BlockState), 4, false) - time.Sleep(time.Millisecond * 100) + time.Sleep(time.Millisecond * 500) setID, err := handler.grandpaState.(*state.GrandpaState).GetCurrentSetID() require.NoError(t, err) require.Equal(t, uint64(1), setID) diff --git a/dot/state/grandpa_changes.go b/dot/state/grandpa_changes.go index 0b95b33ff0..e35bd8dbef 100644 --- a/dot/state/grandpa_changes.go +++ b/dot/state/grandpa_changes.go @@ -81,10 +81,10 @@ func (oc orderedPendingChanges) lookupChangeWhere(condition conditionFunc[*pendi // importChange only tracks the pending change if and only if it is the // unique forced change in its fork, otherwise will return an error -func (oc orderedPendingChanges) importChange(pendingChange *pendingChange, isDescendantOf isDescendantOfFunc) error { +func (oc *orderedPendingChanges) importChange(pendingChange *pendingChange, isDescendantOf isDescendantOfFunc) error { announcingHeader := pendingChange.announcingHeader.Hash() - for _, change := range oc { + for _, change := range *oc { changeBlockHash := change.announcingHeader.Hash() if changeBlockHash.Equal(announcingHeader) { @@ -101,7 +101,7 @@ func (oc orderedPendingChanges) importChange(pendingChange *pendingChange, isDes } } - oc = append(oc, pendingChange) + *oc = append(*oc, pendingChange) sort.Sort(oc) return nil From af22fa7b234a957bed3483299fd5a2952cdf26f5 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Wed, 1 Jun 2022 16:32:10 -0400 Subject: [PATCH 67/93] chore: removing pause/resume unit tests --- dot/digest/digest_test.go | 61 --------------------------------------- 1 file changed, 61 deletions(-) diff --git a/dot/digest/digest_test.go b/dot/digest/digest_test.go index 88593852a9..fe77a789d5 100644 --- a/dot/digest/digest_test.go +++ b/dot/digest/digest_test.go @@ -162,67 +162,6 @@ func TestHandler_GrandpaForcedChange(t *testing.T) { require.Equal(t, expected, auths) } -func TestHandler_GrandpaPauseAndResume(t *testing.T) { - handler, _ := newTestHandler(t) - handler.Start() - defer handler.Stop() - - p := types.GrandpaPause{ - Delay: 3, - } - - var digest = types.NewGrandpaConsensusDigest() - err := digest.Set(p) - require.NoError(t, err) - - data, err := scale.Marshal(digest) - require.NoError(t, err) - - d := &types.ConsensusDigest{ - ConsensusEngineID: types.GrandpaEngineID, - Data: data, - } - - err = handler.handleConsensusDigest(d, nil) - require.NoError(t, err) - nextPause, err := handler.grandpaState.(*state.GrandpaState).GetNextPause() - require.NoError(t, err) - require.Equal(t, uint(p.Delay), nextPause) - - headers, _ := state.AddBlocksToState(t, handler.blockState.(*state.BlockState), 3, false) - for i, h := range headers { - handler.blockState.(*state.BlockState).SetFinalisedHash(h.Hash(), uint64(i), 0) - } - - time.Sleep(time.Millisecond * 100) - - r := types.GrandpaResume{ - Delay: 3, - } - - var digest2 = types.NewGrandpaConsensusDigest() - err = digest2.Set(r) - require.NoError(t, err) - - data, err = scale.Marshal(digest2) - require.NoError(t, err) - - d = &types.ConsensusDigest{ - ConsensusEngineID: types.GrandpaEngineID, - Data: data, - } - - err = handler.handleConsensusDigest(d, nil) - require.NoError(t, err) - - state.AddBlocksToState(t, handler.blockState.(*state.BlockState), 3, false) - time.Sleep(time.Millisecond * 110) - - nextResume, err := handler.grandpaState.(*state.GrandpaState).GetNextResume() - require.NoError(t, err) - require.Equal(t, uint(r.Delay+p.Delay), nextResume) -} - func TestMultipleGRANDPADigests_ShouldIncludeJustForcedChanges(t *testing.T) { tests := map[string]struct { digestsTypes []scale.VaryingDataTypeValue From 398126367bac69d660371aa62e099f0013e4d60c Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Thu, 2 Jun 2022 09:52:00 -0400 Subject: [PATCH 68/93] chore: fix NextGrandpaAuthorityChange rule --- dot/state/grandpa.go | 20 +++++++++++++++----- dot/state/grandpa_test.go | 16 ++++++++-------- lib/grandpa/grandpa.go | 10 +++++++--- lib/grandpa/state.go | 2 +- 4 files changed, 31 insertions(+), 17 deletions(-) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 513373b921..0379c910ad 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -264,9 +264,14 @@ func (s *GrandpaState) ApplyForcedChanges(importedBlockHeader *types.Header) err // NextGrandpaAuthorityChange returns the block number of the next upcoming grandpa authorities change. // It returns 0 if no change is scheduled. -func (s *GrandpaState) NextGrandpaAuthorityChange(bestBlockHash common.Hash) (blockNumber uint, err error) { +func (s *GrandpaState) NextGrandpaAuthorityChange(bestBlockHash common.Hash, bestBlockNumber uint) (blockNumber uint, err error) { forcedChange, err := s.forcedChanges.lookupChangeWhere(func(pc *pendingChange) (bool, error) { - return s.blockState.IsDescendantOf(pc.announcingHeader.Hash(), bestBlockHash) + isDecendant, err := s.blockState.IsDescendantOf(pc.announcingHeader.Hash(), bestBlockHash) + if err != nil { + return false, fmt.Errorf("cannot check ancestry: %w", err) + } + + return isDecendant && pc.effectiveNumber() <= uint(bestBlockNumber), nil }) if err != nil { return 0, fmt.Errorf("cannot get forced change on chain of %s: %w", @@ -274,20 +279,25 @@ func (s *GrandpaState) NextGrandpaAuthorityChange(bestBlockHash common.Hash) (bl } scheduledChangeNode, err := s.scheduledChangeRoots.lookupChangeWhere(func(pcn *pendingChangeNode) (bool, error) { - return s.blockState.IsDescendantOf(pcn.change.announcingHeader.Hash(), bestBlockHash) + isDecendant, err := s.blockState.IsDescendantOf(pcn.change.announcingHeader.Hash(), bestBlockHash) + if err != nil { + return false, fmt.Errorf("cannot check ancestry: %w", err) + } + + return isDecendant && pcn.change.effectiveNumber() <= uint(bestBlockNumber), nil }) if err != nil { return 0, fmt.Errorf("cannot get forced change on chain of %s: %w", bestBlockHash, err) } - var next uint + var next uint = 0 if scheduledChangeNode != nil { next = scheduledChangeNode.change.effectiveNumber() } - if forcedChange != nil && forcedChange.effectiveNumber() < next { + if forcedChange != nil && (forcedChange.effectiveNumber() < next || next == 0) { next = forcedChange.effectiveNumber() } diff --git a/dot/state/grandpa_test.go b/dot/state/grandpa_test.go index 1321dc523f..5b910f4273 100644 --- a/dot/state/grandpa_test.go +++ b/dot/state/grandpa_test.go @@ -236,7 +236,7 @@ func TestAddScheduledChangesKeepTheRightForkTree(t *testing.T) { require.NoError(t, err) } - require.Len(t, gs.scheduledChangeRoots, tt.expectedRoots) + require.Len(t, *gs.scheduledChangeRoots, tt.expectedRoots) for _, root := range *gs.scheduledChangeRoots { parentHash := root.change.announcingHeader.Hash() @@ -546,8 +546,8 @@ func TestNextGrandpaAuthorityChange(t *testing.T) { *tt.scheduledChange) } - lastBlockOnChain := chainHeaders[sizeOfChain].Hash() - blockNumber, err := gs.NextGrandpaAuthorityChange(lastBlockOnChain) + lastBlockOnChain := chainHeaders[sizeOfChain] + blockNumber, err := gs.NextGrandpaAuthorityChange(lastBlockOnChain.Hash(), lastBlockOnChain.Number) if tt.wantErr != nil { require.Error(t, err) @@ -742,7 +742,7 @@ func TestApplyForcedChanges(t *testing.T) { err = gs.ApplyForcedChanges(selectedImportedHeader) if tt.wantErr != nil { require.Error(t, err) - require.EqualError(t, err, tt.wantErr.Error()) + require.ErrorIs(t, err, tt.wantErr) } else { require.NoError(t, err) } @@ -866,7 +866,7 @@ func TestApplyScheduledChangesKeepDescendantForcedChanges(t *testing.T) { } else { require.NoError(t, err) - require.Len(t, gs.forcedChanges, tt.expectedForcedChangesLen) + require.Len(t, *gs.forcedChanges, tt.expectedForcedChangesLen) for _, forcedChange := range *gs.forcedChanges { isDescendant, err := gs.blockState.IsDescendantOf( @@ -1107,7 +1107,7 @@ func TestApplyScheduledChangeGetApplicableChange(t *testing.T) { require.Nil(t, changeNode) } - require.Len(t, gs.scheduledChangeRoots, tt.expectedScheduledChangeRootsLen) + require.Len(t, *gs.scheduledChangeRoots, tt.expectedScheduledChangeRootsLen) // make sure all the next scheduled changes are descendant of the finalized hash assertDescendantChildren(t, selectedHeader.Hash(), gs.blockState.IsDescendantOf, *gs.scheduledChangeRoots) @@ -1336,8 +1336,8 @@ func TestApplyScheduledChange(t *testing.T) { selectedFinalizedHeader.Hash(), gs.blockState.IsDescendantOf, *gs.scheduledChangeRoots) } - require.Len(t, gs.forcedChanges, tt.expectedForcedChangesLen) - require.Len(t, gs.scheduledChangeRoots, tt.expectedScheduledChangeRootsLen) + require.Len(t, *gs.forcedChanges, tt.expectedForcedChangesLen) + require.Len(t, *gs.scheduledChangeRoots, tt.expectedScheduledChangeRootsLen) currentSetID, err := gs.GetCurrentSetID() require.NoError(t, err) diff --git a/lib/grandpa/grandpa.go b/lib/grandpa/grandpa.go index a61d1381af..0c87b28317 100644 --- a/lib/grandpa/grandpa.go +++ b/lib/grandpa/grandpa.go @@ -700,7 +700,7 @@ func (s *Service) determinePreVote() (*Vote, error) { vote = NewVoteFromHeader(bestBlockHeader) } - nextChange, err := s.grandpaState.NextGrandpaAuthorityChange(bestBlockHeader.Hash()) + nextChange, err := s.grandpaState.NextGrandpaAuthorityChange(bestBlockHeader.Hash(), bestBlockHeader.Number) if errors.Is(err, state.ErrNoChanges) { return vote, nil } else if err != nil { @@ -730,8 +730,12 @@ func (s *Service) determinePreCommit() (*Vote, error) { s.preVotedBlock[s.state.round] = &pvb s.mapLock.Unlock() - bestBlockHash := s.blockState.BestBlockHash() - nextChange, err := s.grandpaState.NextGrandpaAuthorityChange(bestBlockHash) + bestBlockHeader, err := s.blockState.BestBlockHeader() + if err != nil { + return nil, fmt.Errorf("cannot retrieve best block header: %w", err) + } + + nextChange, err := s.grandpaState.NextGrandpaAuthorityChange(bestBlockHeader.Hash(), bestBlockHeader.Number) if errors.Is(err, state.ErrNoChanges) { return &pvb, nil } else if err != nil { diff --git a/lib/grandpa/state.go b/lib/grandpa/state.go index db026c22cc..15f85f7722 100644 --- a/lib/grandpa/state.go +++ b/lib/grandpa/state.go @@ -51,7 +51,7 @@ type GrandpaState interface { //nolint:revive SetPrecommits(round, setID uint64, data []SignedVote) error GetPrevotes(round, setID uint64) ([]SignedVote, error) GetPrecommits(round, setID uint64) ([]SignedVote, error) - NextGrandpaAuthorityChange(bestBlockHash common.Hash) (blockHeight uint, err error) + NextGrandpaAuthorityChange(bestBlockHash common.Hash, bestBlockNumber uint) (blockHeight uint, err error) } //go:generate mockery --name Network --structname Network --case underscore --keeptree From aef75e53900f552c35bc892bf0a8234a9d24af27 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Thu, 2 Jun 2022 10:05:50 -0400 Subject: [PATCH 69/93] chore: fix the error assertion --- dot/state/grandpa_changes.go | 2 +- dot/state/grandpa_test.go | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/dot/state/grandpa_changes.go b/dot/state/grandpa_changes.go index e35bd8dbef..f9331c117b 100644 --- a/dot/state/grandpa_changes.go +++ b/dot/state/grandpa_changes.go @@ -184,7 +184,7 @@ func (ct *changeTree) importChange(pendingChange *pendingChange, isDescendantOf pendingChange.announcingHeader.Number, pendingChange, isDescendantOf) if err != nil { - return fmt.Errorf("could not import scheduled change: %w", err) + return err } if imported { diff --git a/dot/state/grandpa_test.go b/dot/state/grandpa_test.go index 5b910f4273..9a32106cad 100644 --- a/dot/state/grandpa_test.go +++ b/dot/state/grandpa_test.go @@ -205,8 +205,10 @@ func TestAddScheduledChangesKeepTheRightForkTree(t *testing.T) { "add_scheduled_changes_with_same_hash": { headersWithScheduledChanges: []headersToAdd{ {header: chainA[3]}, - {header: chainA[3], wantErr: fmt.Errorf("could not import scheduled change: %w", - errDuplicateHashes)}, + { + header: chainA[3], + wantErr: fmt.Errorf("cannot import scheduled change: %w", errDuplicateHashes), + }, }, expectedRoots: 0, }, From 36cc62c18812434dd7f3a03ebec383f0f1eb6bbd Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Thu, 2 Jun 2022 10:25:38 -0400 Subject: [PATCH 70/93] chore: remove unecessary cast and lll --- dot/state/grandpa.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 0379c910ad..02ce47dd1a 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -264,14 +264,15 @@ func (s *GrandpaState) ApplyForcedChanges(importedBlockHeader *types.Header) err // NextGrandpaAuthorityChange returns the block number of the next upcoming grandpa authorities change. // It returns 0 if no change is scheduled. -func (s *GrandpaState) NextGrandpaAuthorityChange(bestBlockHash common.Hash, bestBlockNumber uint) (blockNumber uint, err error) { +func (s *GrandpaState) NextGrandpaAuthorityChange(bestBlockHash common.Hash, bestBlockNumber uint) ( + blockNumber uint, err error) { forcedChange, err := s.forcedChanges.lookupChangeWhere(func(pc *pendingChange) (bool, error) { isDecendant, err := s.blockState.IsDescendantOf(pc.announcingHeader.Hash(), bestBlockHash) if err != nil { return false, fmt.Errorf("cannot check ancestry: %w", err) } - return isDecendant && pc.effectiveNumber() <= uint(bestBlockNumber), nil + return isDecendant && pc.effectiveNumber() <= bestBlockNumber, nil }) if err != nil { return 0, fmt.Errorf("cannot get forced change on chain of %s: %w", @@ -284,15 +285,14 @@ func (s *GrandpaState) NextGrandpaAuthorityChange(bestBlockHash common.Hash, bes return false, fmt.Errorf("cannot check ancestry: %w", err) } - return isDecendant && pcn.change.effectiveNumber() <= uint(bestBlockNumber), nil + return isDecendant && pcn.change.effectiveNumber() <= bestBlockNumber, nil }) if err != nil { return 0, fmt.Errorf("cannot get forced change on chain of %s: %w", bestBlockHash, err) } - var next uint = 0 - + var next uint if scheduledChangeNode != nil { next = scheduledChangeNode.change.effectiveNumber() } From d5857bd6cef2457938c1ef6b302c24c3d7167c55 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Thu, 2 Jun 2022 11:52:35 -0400 Subject: [PATCH 71/93] chore: fix typo --- dot/digest/digest.go | 1 + dot/state/grandpa.go | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/dot/digest/digest.go b/dot/digest/digest.go index c6bb9cdf8a..dee42165f3 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -124,6 +124,7 @@ func checkForGRANDPAForcedChanges(digests []types.ConsensusDigest) ([]types.Cons digestsWithoutScheduled := make([]types.ConsensusDigest, 0, len(digests)) for _, digest := range digests { if digest.ConsensusEngineID != types.GrandpaEngineID { + digestsWithoutScheduled = append(digestsWithoutScheduled, digest) continue } diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 02ce47dd1a..179bec0887 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -95,10 +95,10 @@ func (s *GrandpaState) HandleGRANDPADigest(header *types.Header, digest scale.Va case types.GrandpaOnDisabled: return nil case types.GrandpaPause: - logger.Warn("GRANDPA Pause consensus message not imeplemented yet") + logger.Warn("GRANDPA Pause consensus message not implemented yet") return nil case types.GrandpaResume: - logger.Warn("GRANDPA Resume consensus message not imeplemented yet") + logger.Warn("GRANDPA Resume consensus message not implemented yet") return nil default: return fmt.Errorf("not supported digest") From 86641226337077a53dd57dbd7e41f0cb97879632 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Thu, 2 Jun 2022 11:52:35 -0400 Subject: [PATCH 72/93] chore: fix typo --- dot/digest/digest.go | 1 + dot/state/grandpa.go | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/dot/digest/digest.go b/dot/digest/digest.go index c6bb9cdf8a..dee42165f3 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -124,6 +124,7 @@ func checkForGRANDPAForcedChanges(digests []types.ConsensusDigest) ([]types.Cons digestsWithoutScheduled := make([]types.ConsensusDigest, 0, len(digests)) for _, digest := range digests { if digest.ConsensusEngineID != types.GrandpaEngineID { + digestsWithoutScheduled = append(digestsWithoutScheduled, digest) continue } diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 02ce47dd1a..3a1098de35 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -95,10 +95,10 @@ func (s *GrandpaState) HandleGRANDPADigest(header *types.Header, digest scale.Va case types.GrandpaOnDisabled: return nil case types.GrandpaPause: - logger.Warn("GRANDPA Pause consensus message not imeplemented yet") + logger.Warn("GRANDPA Pause consensus message not implemented yet") return nil case types.GrandpaResume: - logger.Warn("GRANDPA Resume consensus message not imeplemented yet") + logger.Warn("GRANDPA Resume consensus message not implemented yet") return nil default: return fmt.Errorf("not supported digest") @@ -108,7 +108,7 @@ func (s *GrandpaState) HandleGRANDPADigest(header *types.Header, digest scale.Va func (s *GrandpaState) addForcedChange(header *types.Header, fc types.GrandpaForcedChange) error { auths, err := types.GrandpaAuthoritiesRawToAuthorities(fc.Auths) if err != nil { - return fmt.Errorf("cannot parser GRANDPA authorities to raw authorities: %w", err) + return fmt.Errorf("cannot parse GRANDPA authorities to raw authorities: %w", err) } pendingChange := &pendingChange{ From 16f0bfcedece61d3f566bed6146b2a057435d059 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Thu, 2 Jun 2022 12:13:20 -0400 Subject: [PATCH 73/93] chore: use binary search to insert a forced change into a ordered slice --- dot/state/grandpa_changes.go | 21 +++++++++++---------- dot/state/grandpa_test.go | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/dot/state/grandpa_changes.go b/dot/state/grandpa_changes.go index f9331c117b..ca2d8400f4 100644 --- a/dot/state/grandpa_changes.go +++ b/dot/state/grandpa_changes.go @@ -32,13 +32,7 @@ func (p *pendingChange) effectiveNumber() uint { type orderedPendingChanges []*pendingChange -// Less order by effective number and then by block number -func (oc orderedPendingChanges) Len() int { return len(oc) } -func (oc orderedPendingChanges) Swap(i, j int) { oc[i], oc[j] = oc[j], oc[i] } -func (oc orderedPendingChanges) Less(i, j int) bool { - return oc[i].effectiveNumber() < oc[j].effectiveNumber() && - oc[i].announcingHeader.Number < oc[j].announcingHeader.Number -} +func (oc *orderedPendingChanges) Len() int { return len(*oc) } // findApplicable try to retrieve an applicable change from the slice of forced changes func (oc orderedPendingChanges) findApplicable(importedHash common.Hash, importedNumber uint, @@ -101,16 +95,23 @@ func (oc *orderedPendingChanges) importChange(pendingChange *pendingChange, isDe } } - *oc = append(*oc, pendingChange) - sort.Sort(oc) + // Use a binary search to include the pending change in the right position + // of a slice ordered by the effective number and by announcing header number + idxToInsert := sort.Search(oc.Len(), func(i int) bool { + return (*oc)[i].effectiveNumber() >= pendingChange.effectiveNumber() && + (*oc)[i].announcingHeader.Number >= pendingChange.announcingHeader.Number + }) + *oc = append(*oc, pendingChange) + copy((*oc)[idxToInsert+1:], (*oc)[idxToInsert:]) + (*oc)[idxToInsert] = pendingChange return nil } // pruneChanges will remove changes whose are not descendant of the hash argument // this function updates the current state of the change tree func (oc *orderedPendingChanges) pruneChanges(hash common.Hash, isDescendantOf isDescendantOfFunc) error { - onBranchForcedChanges := make([]*pendingChange, 0, len(*oc)) + onBranchForcedChanges := make([]*pendingChange, 0, oc.Len()) for _, forcedChange := range *oc { isDescendant, err := isDescendantOf(hash, forcedChange.announcingHeader.Hash()) diff --git a/dot/state/grandpa_test.go b/dot/state/grandpa_test.go index 9a32106cad..3cb426ad2d 100644 --- a/dot/state/grandpa_test.go +++ b/dot/state/grandpa_test.go @@ -331,7 +331,7 @@ func TestForcedScheduledChangesOrder(t *testing.T) { } forcedChangesSlice := *gs.forcedChanges - for idx := 0; idx < len(forcedChangesSlice)-1; idx++ { + for idx := 0; idx < gs.forcedChanges.Len()-1; idx++ { currentChange := forcedChangesSlice[idx] nextChange := forcedChangesSlice[idx+1] From 6cd04cd1ab089e474416161beba09f9d1dbc1498 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Thu, 2 Jun 2022 15:09:20 -0400 Subject: [PATCH 74/93] core: match error name with errror description --- dot/state/grandpa.go | 6 +++--- dot/state/grandpa_test.go | 2 +- lib/grandpa/grandpa.go | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 3a1098de35..ec2e7bf57a 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -19,9 +19,9 @@ var ( errPendingScheduledChanges = errors.New("pending scheduled changes needs to be applied") errDuplicateHashes = errors.New("duplicated hashes") errAlreadyHasForcedChanges = errors.New("already has a forced change") - errUnfinalizedAncestor = errors.New("ancestor with changes not applied") + errUnfinalizedAncestor = errors.New("unfinalized ancestor") - ErrNoChanges = errors.New("cannot get the next authority change block number") + ErrNoNextChange = errors.New("no next authority change") ) var ( @@ -302,7 +302,7 @@ func (s *GrandpaState) NextGrandpaAuthorityChange(bestBlockHash common.Hash, bes } if next == 0 { - return 0, ErrNoChanges + return 0, ErrNoNextChange } return next, nil diff --git a/dot/state/grandpa_test.go b/dot/state/grandpa_test.go index 3cb426ad2d..e57147f3b0 100644 --- a/dot/state/grandpa_test.go +++ b/dot/state/grandpa_test.go @@ -452,7 +452,7 @@ func TestNextGrandpaAuthorityChange(t *testing.T) { expectedBlockNumber uint }{ "no_forced_change_no_scheduled_change": { - wantErr: ErrNoChanges, + wantErr: ErrNoNextChange, }, "only_forced_change": { forcedChangeAnnoucingIndex: 2, // in the chain headers slice the index 2 == block number 3 diff --git a/lib/grandpa/grandpa.go b/lib/grandpa/grandpa.go index 0c87b28317..726e766f2c 100644 --- a/lib/grandpa/grandpa.go +++ b/lib/grandpa/grandpa.go @@ -701,7 +701,7 @@ func (s *Service) determinePreVote() (*Vote, error) { } nextChange, err := s.grandpaState.NextGrandpaAuthorityChange(bestBlockHeader.Hash(), bestBlockHeader.Number) - if errors.Is(err, state.ErrNoChanges) { + if errors.Is(err, state.ErrNoNextChange) { return vote, nil } else if err != nil { return nil, fmt.Errorf("cannot get next grandpa authority change: %w", err) @@ -736,7 +736,7 @@ func (s *Service) determinePreCommit() (*Vote, error) { } nextChange, err := s.grandpaState.NextGrandpaAuthorityChange(bestBlockHeader.Hash(), bestBlockHeader.Number) - if errors.Is(err, state.ErrNoChanges) { + if errors.Is(err, state.ErrNoNextChange) { return &pvb, nil } else if err != nil { return nil, fmt.Errorf("cannot get next grandpa authority change: %w", err) From 8acc021d1fedfe2741cbcd77bdb050f6fbb52c73 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Thu, 2 Jun 2022 15:10:32 -0400 Subject: [PATCH 75/93] chore: fix error wrapper description --- dot/state/grandpa_changes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dot/state/grandpa_changes.go b/dot/state/grandpa_changes.go index ca2d8400f4..0014cb4115 100644 --- a/dot/state/grandpa_changes.go +++ b/dot/state/grandpa_changes.go @@ -159,7 +159,7 @@ func (c *pendingChangeNode) importNode(blockHash common.Hash, blockNumber uint, isDescendant, err := isDescendantOf(announcingHash, blockHash) if err != nil { - return false, fmt.Errorf("cannot define ancestry: %w", err) + return false, fmt.Errorf("cannot check ancestry: %w", err) } if !isDescendant { From 698d001b23ead701091b0a42c7885a47353a6f3a Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Thu, 2 Jun 2022 15:16:42 -0400 Subject: [PATCH 76/93] chore: improving the error wrapper description --- dot/state/grandpa_changes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dot/state/grandpa_changes.go b/dot/state/grandpa_changes.go index 0014cb4115..d6ebb944c4 100644 --- a/dot/state/grandpa_changes.go +++ b/dot/state/grandpa_changes.go @@ -149,7 +149,7 @@ func (c *pendingChangeNode) importNode(blockHash common.Hash, blockNumber uint, for _, childrenNodes := range c.nodes { imported, err := childrenNodes.importNode(blockHash, blockNumber, pendingChange, isDescendantOf) if err != nil { - return false, err + return false, fmt.Errorf("cannot track node: %w", err) } if imported { From 0ef631e0ae7562f7a9fe234598150fea0881dadb Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Thu, 2 Jun 2022 15:25:43 -0400 Subject: [PATCH 77/93] chore: `HandleDigest` return error and log in `handleBlockImport` --- dot/digest/digest.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/dot/digest/digest.go b/dot/digest/digest.go index dee42165f3..22081b49d1 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -77,12 +77,11 @@ func (h *Handler) Stop() error { } // HandleDigests handles consensus digests for an imported block -func (h *Handler) HandleDigests(header *types.Header) { +func (h *Handler) HandleDigests(header *types.Header) error { consensusDigests := h.toConsensusDigests(header.Digest.Types) consensusDigests, err := checkForGRANDPAForcedChanges(consensusDigests) if err != nil { - h.logger.Errorf("cannot ignore multiple GRANDPA digests: %s", err) - return + return fmt.Errorf("failed while checking GRANDPA digests: %w", err) } for i := range consensusDigests { @@ -95,6 +94,8 @@ func (h *Handler) HandleDigests(header *types.Header) { h.logger.Errorf("cannot handle consensus digest: %w", err) } } + + return nil } // toConsensusDigests converts a slice of scale.VaryingDataType to a slice of types.ConsensusDigest. @@ -217,8 +218,12 @@ func (h *Handler) handleBlockImport(ctx context.Context) { continue } - h.HandleDigests(&block.Header) - err := h.grandpaState.ApplyForcedChanges(&block.Header) + err := h.HandleDigests(&block.Header) + if err != nil { + h.logger.Errorf("failed to handle digest: %s", err) + } + + err = h.grandpaState.ApplyForcedChanges(&block.Header) if err != nil { h.logger.Errorf("failed to apply forced changes: %s", err) } From f534e36d67bcd8dabe7fa9c4e3565f2d3ff70755 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Thu, 2 Jun 2022 15:30:52 -0400 Subject: [PATCH 78/93] chore: remove a not needed return --- dot/state/grandpa.go | 4 ++-- dot/state/service.go | 6 +----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index ec2e7bf57a..02a9fa530d 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -76,13 +76,13 @@ func NewGrandpaStateFromGenesis(db chaindb.Database, bs *BlockState, } // NewGrandpaState returns a new GrandpaState -func NewGrandpaState(db chaindb.Database, bs *BlockState) (*GrandpaState, error) { +func NewGrandpaState(db chaindb.Database, bs *BlockState) *GrandpaState { return &GrandpaState{ db: chaindb.NewTable(db, grandpaPrefix), blockState: bs, scheduledChangeRoots: new(changeTree), forcedChanges: new(orderedPendingChanges), - }, nil + } } // HandleGRANDPADigest receives a decoded GRANDPA digest and calls the right function to handles the digest diff --git a/dot/state/service.go b/dot/state/service.go index 272c3f0697..6794fee251 100644 --- a/dot/state/service.go +++ b/dot/state/service.go @@ -159,11 +159,7 @@ func (s *Service) Start() error { return fmt.Errorf("failed to create epoch state: %w", err) } - s.Grandpa, err = NewGrandpaState(s.db, s.Block) - if err != nil { - return fmt.Errorf("failed to create grandpa state: %w", err) - } - + s.Grandpa = NewGrandpaState(s.db, s.Block) num, _ := s.Block.BestBlockNumber() logger.Infof( "created state service with head %s, highest number %d and genesis hash %s", From 575486aa14ce92606e62d0e47a781b9f6328563f Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Thu, 2 Jun 2022 16:03:46 -0400 Subject: [PATCH 79/93] chore: ensure the remaining changes belongs to the same chain as finalized header --- dot/state/grandpa_changes.go | 12 +++++------- dot/state/grandpa_test.go | 5 +++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/dot/state/grandpa_changes.go b/dot/state/grandpa_changes.go index d6ebb944c4..feba7a547d 100644 --- a/dot/state/grandpa_changes.go +++ b/dot/state/grandpa_changes.go @@ -124,8 +124,7 @@ func (oc *orderedPendingChanges) pruneChanges(hash common.Hash, isDescendantOf i } } - *oc = make(orderedPendingChanges, len(onBranchForcedChanges)) - copy(*oc, onBranchForcedChanges) + *oc = onBranchForcedChanges return nil } @@ -231,7 +230,7 @@ func (ct *changeTree) findApplicable(hash common.Hash, number uint, changeNode, err = ct.findApplicableChange(hash, number, isDescendantOf) if err != nil { - return nil, fmt.Errorf("cannot find applicable change: %w", err) + return nil, err } if changeNode == nil { @@ -290,10 +289,10 @@ func (ct changeTree) findApplicableChange(hash common.Hash, number uint, // pruneChanges will remove changes whose are not descendant of the hash argument // this function updates the current state of the change tree -func (ct changeTree) pruneChanges(hash common.Hash, isDescendantOf isDescendantOfFunc) error { +func (ct *changeTree) pruneChanges(hash common.Hash, isDescendantOf isDescendantOfFunc) error { onBranchChanges := []*pendingChangeNode{} - for _, root := range ct { + for _, root := range *ct { scheduledChangeHash := root.change.announcingHeader.Hash() isDescendant, err := isDescendantOf(hash, scheduledChangeHash) @@ -306,7 +305,6 @@ func (ct changeTree) pruneChanges(hash common.Hash, isDescendantOf isDescendantO } } - ct = make([]*pendingChangeNode, len(onBranchChanges)) - copy(ct, onBranchChanges) + *ct = onBranchChanges return nil } diff --git a/dot/state/grandpa_test.go b/dot/state/grandpa_test.go index e57147f3b0..929aad494e 100644 --- a/dot/state/grandpa_test.go +++ b/dot/state/grandpa_test.go @@ -1219,8 +1219,9 @@ func TestApplyScheduledChange(t *testing.T) { }, }) }, - finalizedHeader: [2]int{0, 6}, // finalize block number 7 from chain A - wantErr: fmt.Errorf("cannot finalize scheduled change: %w", errUnfinalizedAncestor), + finalizedHeader: [2]int{0, 6}, // finalize block number 7 from chain A + wantErr: fmt.Errorf( + "cannot get applicable scheduled change: failed while applying condition: %w", errUnfinalizedAncestor), expectedScheduledChangeRootsLen: 1, // expected one root len as the second change is a child expectedAuthoritySet: func() []types.GrandpaVoter { auths, _ := types.GrandpaAuthoritiesRawToAuthorities(genesisGrandpaVoters) From be0e9e56745b3068320aa5b8c30fae725979d1d4 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Fri, 3 Jun 2022 09:24:08 -0400 Subject: [PATCH 80/93] chore: solve build failing --- go.sum | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.sum b/go.sum index 8c8e35d9e8..d9e2aa7f9a 100644 --- a/go.sum +++ b/go.sum @@ -1153,8 +1153,8 @@ github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34= -github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= From bd35350b1966cbda9c7bec93f04ff5ced8913a70 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Fri, 3 Jun 2022 10:20:23 -0400 Subject: [PATCH 81/93] chore: testing with the right wrapper --- dot/state/grandpa_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dot/state/grandpa_test.go b/dot/state/grandpa_test.go index 929aad494e..94aa0630f6 100644 --- a/dot/state/grandpa_test.go +++ b/dot/state/grandpa_test.go @@ -1064,7 +1064,7 @@ func TestApplyScheduledChangeGetApplicableChange(t *testing.T) { }) }, finalizedHeader: [2]int{0, 6}, // finalize block number 7 from chain A - wantErr: errUnfinalizedAncestor, + wantErr: fmt.Errorf("failed while applying condition: %w", errUnfinalizedAncestor), }, } From 9fece6e0d6fae073aaf9b8c36079d31e54611a85 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Fri, 3 Jun 2022 16:13:27 -0400 Subject: [PATCH 82/93] chore: resolving race conditions --- dot/state/grandpa_test.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/dot/state/grandpa_test.go b/dot/state/grandpa_test.go index 94aa0630f6..cc36a07716 100644 --- a/dot/state/grandpa_test.go +++ b/dot/state/grandpa_test.go @@ -1127,12 +1127,8 @@ func TestApplyScheduledChange(t *testing.T) { {Key: keyring.KeyCharlie.Public().(*sr25519.PublicKey).AsBytes()}, } - genesisAuths, err := types.GrandpaAuthoritiesRawToAuthorities(genesisGrandpaVoters) - require.NoError(t, err) - const sizeOfChain = 10 genericForks := func(t *testing.T, blockState *BlockState) [][]*types.Header { - /* * create chainA and two forks: chainB and chainC * @@ -1305,6 +1301,9 @@ func TestApplyScheduledChange(t *testing.T) { db := NewInMemoryDB(t) blockState := testBlockState(t, db) + genesisAuths, err := types.GrandpaAuthoritiesRawToAuthorities(genesisGrandpaVoters) + require.NoError(t, err) + voters := types.NewGrandpaVotersFromAuthorities(genesisAuths) gs, err := NewGrandpaStateFromGenesis(db, blockState, voters) require.NoError(t, err) From cffb6ec45f458e2adebb2f3017b6836b44fe8ee1 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Mon, 6 Jun 2022 10:06:33 -0400 Subject: [PATCH 83/93] chore: fix race condition in unit tests --- dot/state/grandpa_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dot/state/grandpa_test.go b/dot/state/grandpa_test.go index cc36a07716..cfc9c62586 100644 --- a/dot/state/grandpa_test.go +++ b/dot/state/grandpa_test.go @@ -1206,7 +1206,7 @@ func TestApplyScheduledChange(t *testing.T) { // change on block 5 from chain A should be a child // node of scheduled change on block 4 from chain A chainABlock5 := headers[0][5] - err = gs.addScheduledChange(chainABlock5, types.GrandpaScheduledChange{ + gs.addScheduledChange(chainABlock5, types.GrandpaScheduledChange{ Delay: 0, Auths: []types.GrandpaAuthoritiesRaw{ {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, @@ -1238,7 +1238,7 @@ func TestApplyScheduledChange(t *testing.T) { }) chainBBlock8 := headers[1][5] // block number 8 from chain B - err = gs.addScheduledChange(chainBBlock8, types.GrandpaScheduledChange{ + gs.addScheduledChange(chainBBlock8, types.GrandpaScheduledChange{ Delay: 0, Auths: []types.GrandpaAuthoritiesRaw{ {Key: keyring.KeyBob.Public().(*sr25519.PublicKey).AsBytes()}, From 8e88f42b2cb31d7156551357353340543f6cc193 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Tue, 7 Jun 2022 16:33:34 -0400 Subject: [PATCH 84/93] chore: addressing var changing name and wrapper errors --- dot/digest/digest.go | 4 ++-- dot/state/grandpa.go | 6 +++--- dot/state/grandpa_changes.go | 27 +++++++++++++++------------ dot/state/grandpa_test.go | 4 ++-- lib/grandpa/grandpa.go | 4 ++-- 5 files changed, 24 insertions(+), 21 deletions(-) diff --git a/dot/digest/digest.go b/dot/digest/digest.go index 22081b49d1..a8e2578c45 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -19,7 +19,7 @@ var ( ) var ( - ErrUnknownConsensusID = errors.New("unknown consensus engine ID") + ErrUnknownConsensusEngineID = errors.New("unknown consensus engine ID") ) // Handler is used to handle consensus messages and relevant authority updates to BABE and GRANDPA @@ -171,7 +171,7 @@ func (h *Handler) handleConsensusDigest(d *types.ConsensusDigest, header *types. return h.handleBabeConsensusDigest(data, header) default: - return fmt.Errorf("%w: 0x%x", ErrUnknownConsensusID, d.ConsensusEngineID.ToBytes()) + return fmt.Errorf("%w: 0x%x", ErrUnknownConsensusEngineID, d.ConsensusEngineID.ToBytes()) } } diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index 02a9fa530d..1464bcb0a6 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -18,10 +18,10 @@ import ( var ( errPendingScheduledChanges = errors.New("pending scheduled changes needs to be applied") errDuplicateHashes = errors.New("duplicated hashes") - errAlreadyHasForcedChanges = errors.New("already has a forced change") + errAlreadyHasForcedChange = errors.New("already has a forced change") errUnfinalizedAncestor = errors.New("unfinalized ancestor") - ErrNoNextChange = errors.New("no next authority change") + ErrNoNextAuthorityChange = errors.New("no next authority change") ) var ( @@ -302,7 +302,7 @@ func (s *GrandpaState) NextGrandpaAuthorityChange(bestBlockHash common.Hash, bes } if next == 0 { - return 0, ErrNoNextChange + return 0, ErrNoNextAuthorityChange } return next, nil diff --git a/dot/state/grandpa_changes.go b/dot/state/grandpa_changes.go index feba7a547d..5725c9afa4 100644 --- a/dot/state/grandpa_changes.go +++ b/dot/state/grandpa_changes.go @@ -82,7 +82,7 @@ func (oc *orderedPendingChanges) importChange(pendingChange *pendingChange, isDe changeBlockHash := change.announcingHeader.Hash() if changeBlockHash.Equal(announcingHeader) { - return errDuplicateHashes + return fmt.Errorf("%w: %s", errDuplicateHashes, changeBlockHash) } isDescendant, err := isDescendantOf(changeBlockHash, announcingHeader) @@ -91,20 +91,24 @@ func (oc *orderedPendingChanges) importChange(pendingChange *pendingChange, isDe } if isDescendant { - return errAlreadyHasForcedChanges + return fmt.Errorf("%w: for block hash %s", errAlreadyHasForcedChange, changeBlockHash) } } + orderedPendingChanges := *oc + // Use a binary search to include the pending change in the right position // of a slice ordered by the effective number and by announcing header number idxToInsert := sort.Search(oc.Len(), func(i int) bool { - return (*oc)[i].effectiveNumber() >= pendingChange.effectiveNumber() && - (*oc)[i].announcingHeader.Number >= pendingChange.announcingHeader.Number + return orderedPendingChanges[i].effectiveNumber() >= pendingChange.effectiveNumber() && + orderedPendingChanges[i].announcingHeader.Number >= pendingChange.announcingHeader.Number }) - *oc = append(*oc, pendingChange) - copy((*oc)[idxToInsert+1:], (*oc)[idxToInsert:]) - (*oc)[idxToInsert] = pendingChange + orderedPendingChanges = append(orderedPendingChanges, pendingChange) + copy(orderedPendingChanges[idxToInsert+1:], orderedPendingChanges[idxToInsert:]) + orderedPendingChanges[idxToInsert] = pendingChange + *oc = orderedPendingChanges + return nil } @@ -138,7 +142,7 @@ func (c *pendingChangeNode) importNode(blockHash common.Hash, blockNumber uint, announcingHash := c.change.announcingHeader.Hash() if blockHash.Equal(announcingHash) { - return false, errDuplicateHashes + return false, fmt.Errorf("%w: %s", errDuplicateHashes, blockHash) } if blockNumber <= c.change.announcingHeader.Number { @@ -148,7 +152,7 @@ func (c *pendingChangeNode) importNode(blockHash common.Hash, blockNumber uint, for _, childrenNodes := range c.nodes { imported, err := childrenNodes.importNode(blockHash, blockNumber, pendingChange, isDescendantOf) if err != nil { - return false, fmt.Errorf("cannot track node: %w", err) + return false, fmt.Errorf("cannot import node: %w", err) } if imported { @@ -165,8 +169,8 @@ func (c *pendingChangeNode) importNode(blockHash common.Hash, blockNumber uint, return false, nil } - pendingChangeNode := &pendingChangeNode{change: pendingChange, nodes: []*pendingChangeNode{}} - c.nodes = append(c.nodes, pendingChangeNode) + childrenNode := &pendingChangeNode{change: pendingChange} + c.nodes = append(c.nodes, childrenNode) return true, nil } @@ -196,7 +200,6 @@ func (ct *changeTree) importChange(pendingChange *pendingChange, isDescendantOf pendingChangeNode := &pendingChangeNode{ change: pendingChange, - nodes: []*pendingChangeNode{}, } *ct = append(*ct, pendingChangeNode) diff --git a/dot/state/grandpa_test.go b/dot/state/grandpa_test.go index cfc9c62586..b9699ceeb0 100644 --- a/dot/state/grandpa_test.go +++ b/dot/state/grandpa_test.go @@ -376,7 +376,7 @@ func TestShouldNotAddMoreThanOneForcedChangeInTheSameFork(t *testing.T) { err = gs.addForcedChange(aliceHeaders[4], someForcedChange) require.Error(t, err) - require.ErrorIs(t, err, errAlreadyHasForcedChanges) + require.ErrorIs(t, err, errAlreadyHasForcedChange) // adding the same forced change twice err = gs.addForcedChange(bobHeaders[2], someForcedChange) @@ -452,7 +452,7 @@ func TestNextGrandpaAuthorityChange(t *testing.T) { expectedBlockNumber uint }{ "no_forced_change_no_scheduled_change": { - wantErr: ErrNoNextChange, + wantErr: ErrNoNextAuthorityChange, }, "only_forced_change": { forcedChangeAnnoucingIndex: 2, // in the chain headers slice the index 2 == block number 3 diff --git a/lib/grandpa/grandpa.go b/lib/grandpa/grandpa.go index 726e766f2c..058ce2117b 100644 --- a/lib/grandpa/grandpa.go +++ b/lib/grandpa/grandpa.go @@ -701,7 +701,7 @@ func (s *Service) determinePreVote() (*Vote, error) { } nextChange, err := s.grandpaState.NextGrandpaAuthorityChange(bestBlockHeader.Hash(), bestBlockHeader.Number) - if errors.Is(err, state.ErrNoNextChange) { + if errors.Is(err, state.ErrNoNextAuthorityChange) { return vote, nil } else if err != nil { return nil, fmt.Errorf("cannot get next grandpa authority change: %w", err) @@ -736,7 +736,7 @@ func (s *Service) determinePreCommit() (*Vote, error) { } nextChange, err := s.grandpaState.NextGrandpaAuthorityChange(bestBlockHeader.Hash(), bestBlockHeader.Number) - if errors.Is(err, state.ErrNoNextChange) { + if errors.Is(err, state.ErrNoNextAuthorityChange) { return &pvb, nil } else if err != nil { return nil, fmt.Errorf("cannot get next grandpa authority change: %w", err) From 6ff15d3f3fac33527a706fe6aa8845576867ca6f Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Tue, 7 Jun 2022 16:40:54 -0400 Subject: [PATCH 85/93] chore: removing recursive error wrapper --- dot/digest/digest.go | 2 +- dot/state/grandpa_changes.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dot/digest/digest.go b/dot/digest/digest.go index a8e2578c45..2d6d8203fa 100644 --- a/dot/digest/digest.go +++ b/dot/digest/digest.go @@ -220,7 +220,7 @@ func (h *Handler) handleBlockImport(ctx context.Context) { err := h.HandleDigests(&block.Header) if err != nil { - h.logger.Errorf("failed to handle digest: %s", err) + h.logger.Errorf("failed to handle digests: %s", err) } err = h.grandpaState.ApplyForcedChanges(&block.Header) diff --git a/dot/state/grandpa_changes.go b/dot/state/grandpa_changes.go index 5725c9afa4..d9e43b8140 100644 --- a/dot/state/grandpa_changes.go +++ b/dot/state/grandpa_changes.go @@ -152,7 +152,7 @@ func (c *pendingChangeNode) importNode(blockHash common.Hash, blockNumber uint, for _, childrenNodes := range c.nodes { imported, err := childrenNodes.importNode(blockHash, blockNumber, pendingChange, isDescendantOf) if err != nil { - return false, fmt.Errorf("cannot import node: %w", err) + return false, err } if imported { From 6676efb3403356e37b5cb73895eedfa83eed22d8 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Tue, 7 Jun 2022 16:47:07 -0400 Subject: [PATCH 86/93] chore: avoiding shadowing --- dot/state/grandpa_changes.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dot/state/grandpa_changes.go b/dot/state/grandpa_changes.go index d9e43b8140..1d552a51ed 100644 --- a/dot/state/grandpa_changes.go +++ b/dot/state/grandpa_changes.go @@ -95,19 +95,19 @@ func (oc *orderedPendingChanges) importChange(pendingChange *pendingChange, isDe } } - orderedPendingChanges := *oc + orderedChanges := *oc // Use a binary search to include the pending change in the right position // of a slice ordered by the effective number and by announcing header number idxToInsert := sort.Search(oc.Len(), func(i int) bool { - return orderedPendingChanges[i].effectiveNumber() >= pendingChange.effectiveNumber() && - orderedPendingChanges[i].announcingHeader.Number >= pendingChange.announcingHeader.Number + return orderedChanges[i].effectiveNumber() >= pendingChange.effectiveNumber() && + orderedChanges[i].announcingHeader.Number >= pendingChange.announcingHeader.Number }) - orderedPendingChanges = append(orderedPendingChanges, pendingChange) - copy(orderedPendingChanges[idxToInsert+1:], orderedPendingChanges[idxToInsert:]) - orderedPendingChanges[idxToInsert] = pendingChange - *oc = orderedPendingChanges + orderedChanges = append(orderedChanges, pendingChange) + copy(orderedChanges[idxToInsert+1:], orderedChanges[idxToInsert:]) + orderedChanges[idxToInsert] = pendingChange + *oc = orderedChanges return nil } From cac95b3804644cba4e3ad2b91d47f1873568815d Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Fri, 10 Jun 2022 15:24:25 -0400 Subject: [PATCH 87/93] chore: fix typo --- 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 1464bcb0a6..e10bf070b8 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -227,7 +227,7 @@ func (s *GrandpaState) ApplyForcedChanges(importedBlockHeader *types.Header) err logger.Debugf("applying forced change: %s", forcedChange) - // send the telemetry s messages here + // TODO: send the telemetry messages here // afg.applying_forced_authority_set_change currentSetID, err := s.GetCurrentSetID() From 82adadb6f3839edf57c053fd0a5642e7e4f40aa2 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Mon, 13 Jun 2022 10:52:18 -0400 Subject: [PATCH 88/93] chore: fix `add_scheduled_changes_with_same_hash` unit test --- dot/state/grandpa.go | 4 ++-- dot/state/grandpa_changes.go | 14 +++++++------- dot/state/grandpa_test.go | 5 +++-- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/dot/state/grandpa.go b/dot/state/grandpa.go index e10bf070b8..50fbce2536 100644 --- a/dot/state/grandpa.go +++ b/dot/state/grandpa.go @@ -111,7 +111,7 @@ func (s *GrandpaState) addForcedChange(header *types.Header, fc types.GrandpaFor return fmt.Errorf("cannot parse GRANDPA authorities to raw authorities: %w", err) } - pendingChange := &pendingChange{ + pendingChange := pendingChange{ bestFinalizedNumber: fc.BestFinalizedBlock, nextAuthorities: auths, announcingHeader: header, @@ -266,7 +266,7 @@ func (s *GrandpaState) ApplyForcedChanges(importedBlockHeader *types.Header) err // It returns 0 if no change is scheduled. func (s *GrandpaState) NextGrandpaAuthorityChange(bestBlockHash common.Hash, bestBlockNumber uint) ( blockNumber uint, err error) { - forcedChange, err := s.forcedChanges.lookupChangeWhere(func(pc *pendingChange) (bool, error) { + forcedChange, err := s.forcedChanges.lookupChangeWhere(func(pc pendingChange) (bool, error) { isDecendant, err := s.blockState.IsDescendantOf(pc.announcingHeader.Hash(), bestBlockHash) if err != nil { return false, fmt.Errorf("cannot check ancestry: %w", err) diff --git a/dot/state/grandpa_changes.go b/dot/state/grandpa_changes.go index 1d552a51ed..2244d0ce17 100644 --- a/dot/state/grandpa_changes.go +++ b/dot/state/grandpa_changes.go @@ -30,7 +30,7 @@ func (p *pendingChange) effectiveNumber() uint { return p.announcingHeader.Number + uint(p.delay) } -type orderedPendingChanges []*pendingChange +type orderedPendingChanges []pendingChange func (oc *orderedPendingChanges) Len() int { return len(*oc) } @@ -38,7 +38,7 @@ func (oc *orderedPendingChanges) Len() int { return len(*oc) } func (oc orderedPendingChanges) findApplicable(importedHash common.Hash, importedNumber uint, isDescendatOf isDescendantOfFunc) (*pendingChange, error) { - return oc.lookupChangeWhere(func(forced *pendingChange) (bool, error) { + return oc.lookupChangeWhere(func(forced pendingChange) (bool, error) { announcingHash := forced.announcingHeader.Hash() effectiveNumber := forced.effectiveNumber() @@ -57,16 +57,16 @@ func (oc orderedPendingChanges) findApplicable(importedHash common.Hash, importe } // lookupChangeWhere return the first pending change which satisfy the condition -func (oc orderedPendingChanges) lookupChangeWhere(condition conditionFunc[*pendingChange]) ( +func (oc orderedPendingChanges) lookupChangeWhere(condition conditionFunc[pendingChange]) ( pendingChange *pendingChange, err error) { for _, change := range oc { ok, err := condition(change) if err != nil { - return nil, fmt.Errorf("failed while applying condition: %w", err) + return pendingChange, fmt.Errorf("failed while applying condition: %w", err) } if ok { - return change, nil + return &change, nil } } @@ -75,7 +75,7 @@ func (oc orderedPendingChanges) lookupChangeWhere(condition conditionFunc[*pendi // importChange only tracks the pending change if and only if it is the // unique forced change in its fork, otherwise will return an error -func (oc *orderedPendingChanges) importChange(pendingChange *pendingChange, isDescendantOf isDescendantOfFunc) error { +func (oc *orderedPendingChanges) importChange(pendingChange pendingChange, isDescendantOf isDescendantOfFunc) error { announcingHeader := pendingChange.announcingHeader.Hash() for _, change := range *oc { @@ -115,7 +115,7 @@ func (oc *orderedPendingChanges) importChange(pendingChange *pendingChange, isDe // pruneChanges will remove changes whose are not descendant of the hash argument // this function updates the current state of the change tree func (oc *orderedPendingChanges) pruneChanges(hash common.Hash, isDescendantOf isDescendantOfFunc) error { - onBranchForcedChanges := make([]*pendingChange, 0, oc.Len()) + onBranchForcedChanges := make([]pendingChange, 0, oc.Len()) for _, forcedChange := range *oc { isDescendant, err := isDescendantOf(hash, forcedChange.announcingHeader.Hash()) diff --git a/dot/state/grandpa_test.go b/dot/state/grandpa_test.go index b9699ceeb0..7b58148b50 100644 --- a/dot/state/grandpa_test.go +++ b/dot/state/grandpa_test.go @@ -206,8 +206,9 @@ func TestAddScheduledChangesKeepTheRightForkTree(t *testing.T) { headersWithScheduledChanges: []headersToAdd{ {header: chainA[3]}, { - header: chainA[3], - wantErr: fmt.Errorf("cannot import scheduled change: %w", errDuplicateHashes), + header: chainA[3], + wantErr: fmt.Errorf("cannot import scheduled change: %w: %s", + errDuplicateHashes, chainA[3].Hash()), }, }, expectedRoots: 0, From 3302cd5caf01f7c7673f0c9a318c0cfc91f71975 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Mon, 13 Jun 2022 11:20:33 -0400 Subject: [PATCH 89/93] chore: add comments and check ancestry first --- dot/state/grandpa_changes.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/dot/state/grandpa_changes.go b/dot/state/grandpa_changes.go index 2244d0ce17..f28ef37339 100644 --- a/dot/state/grandpa_changes.go +++ b/dot/state/grandpa_changes.go @@ -137,6 +137,9 @@ type pendingChangeNode struct { nodes []*pendingChangeNode } +// importNode method is called recursivelly until we found a node that import the pending change as one of +// its children. The node which should import the pending change must be a ancestor with a +// lower block number than the pending change. func (c *pendingChangeNode) importNode(blockHash common.Hash, blockNumber uint, pendingChange *pendingChange, isDescendantOf isDescendantOfFunc) (imported bool, err error) { announcingHash := c.change.announcingHeader.Hash() @@ -145,6 +148,15 @@ func (c *pendingChangeNode) importNode(blockHash common.Hash, blockNumber uint, return false, fmt.Errorf("%w: %s", errDuplicateHashes, blockHash) } + isDescendant, err := isDescendantOf(announcingHash, blockHash) + if err != nil { + return false, fmt.Errorf("cannot check ancestry: %w", err) + } + + if !isDescendant { + return false, nil + } + if blockNumber <= c.change.announcingHeader.Number { return false, nil } @@ -160,15 +172,6 @@ func (c *pendingChangeNode) importNode(blockHash common.Hash, blockNumber uint, } } - isDescendant, err := isDescendantOf(announcingHash, blockHash) - if err != nil { - return false, fmt.Errorf("cannot check ancestry: %w", err) - } - - if !isDescendant { - return false, nil - } - childrenNode := &pendingChangeNode{change: pendingChange} c.nodes = append(c.nodes, childrenNode) return true, nil From da4c66aa57a434f640f7183cef64525c7df5bb58 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Mon, 27 Jun 2022 17:18:35 -0400 Subject: [PATCH 90/93] chore: remove `digest handler` from `createGRANDPAService` --- dot/mock_node_builder_test.go | 8 ++++---- dot/node.go | 4 ++-- dot/node_test.go | 2 +- dot/services.go | 4 ++-- dot/services_integration_test.go | 5 +---- dot/services_test.go | 2 +- 6 files changed, 11 insertions(+), 14 deletions(-) diff --git a/dot/mock_node_builder_test.go b/dot/mock_node_builder_test.go index 569d20cf31..7c08c9f0dc 100644 --- a/dot/mock_node_builder_test.go +++ b/dot/mock_node_builder_test.go @@ -108,18 +108,18 @@ func (mr *MocknodeBuilderIfaceMockRecorder) createDigestHandler(lvl, st interfac } // createGRANDPAService mocks base method. -func (m *MocknodeBuilderIface) createGRANDPAService(cfg *Config, st *state.Service, dh *digest.Handler, ks keystore.Keystore, net *network.Service, telemetryMailer telemetry.Client) (*grandpa.Service, error) { +func (m *MocknodeBuilderIface) createGRANDPAService(cfg *Config, st *state.Service, ks keystore.Keystore, net *network.Service, telemetryMailer telemetry.Client) (*grandpa.Service, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "createGRANDPAService", cfg, st, dh, ks, net, telemetryMailer) + ret := m.ctrl.Call(m, "createGRANDPAService", cfg, st, ks, net, telemetryMailer) ret0, _ := ret[0].(*grandpa.Service) ret1, _ := ret[1].(error) return ret0, ret1 } // createGRANDPAService indicates an expected call of createGRANDPAService. -func (mr *MocknodeBuilderIfaceMockRecorder) createGRANDPAService(cfg, st, dh, ks, net, telemetryMailer interface{}) *gomock.Call { +func (mr *MocknodeBuilderIfaceMockRecorder) createGRANDPAService(cfg, st, ks, net, telemetryMailer interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "createGRANDPAService", reflect.TypeOf((*MocknodeBuilderIface)(nil).createGRANDPAService), cfg, st, dh, ks, net, telemetryMailer) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "createGRANDPAService", reflect.TypeOf((*MocknodeBuilderIface)(nil).createGRANDPAService), cfg, st, ks, net, telemetryMailer) } // createNetworkService mocks base method. diff --git a/dot/node.go b/dot/node.go index 0a5a0f9264..86c6aa7de5 100644 --- a/dot/node.go +++ b/dot/node.go @@ -62,7 +62,7 @@ type nodeBuilderIface interface { createDigestHandler(lvl log.Level, st *state.Service) (*digest.Handler, error) createCoreService(cfg *Config, ks *keystore.GlobalKeystore, st *state.Service, net *network.Service, dh *digest.Handler) (*core.Service, error) - createGRANDPAService(cfg *Config, st *state.Service, dh *digest.Handler, ks keystore.Keystore, + createGRANDPAService(cfg *Config, st *state.Service, ks keystore.Keystore, net *network.Service, telemetryMailer telemetry.Client) (*grandpa.Service, error) newSyncService(cfg *Config, st *state.Service, fg dotsync.FinalityGadget, verifier *babe.VerificationManager, cs *core.Service, net *network.Service, telemetryMailer telemetry.Client) (*dotsync.Service, error) @@ -341,7 +341,7 @@ func newNode(cfg *Config, } nodeSrvcs = append(nodeSrvcs, coreSrvc) - fg, err := builder.createGRANDPAService(cfg, stateSrvc, dh, ks.Gran, networkSrvc, telemetryMailer) + fg, err := builder.createGRANDPAService(cfg, stateSrvc, ks.Gran, networkSrvc, telemetryMailer) if err != nil { return nil, err } diff --git a/dot/node_test.go b/dot/node_test.go index 1a19e40ee8..6f56557797 100644 --- a/dot/node_test.go +++ b/dot/node_test.go @@ -197,7 +197,7 @@ func TestNewNode(t *testing.T) { gomock.AssignableToTypeOf(&network.Service{}), &digest.Handler{}). Return(&core.Service{}, nil) m.EXPECT().createGRANDPAService(dotConfig, gomock.AssignableToTypeOf(&state.Service{}), - &digest.Handler{}, ks.Gran, gomock.AssignableToTypeOf(&network.Service{}), + ks.Gran, gomock.AssignableToTypeOf(&network.Service{}), gomock.AssignableToTypeOf(&telemetry.Mailer{})). Return(&grandpa.Service{}, nil) m.EXPECT().newSyncService(dotConfig, gomock.AssignableToTypeOf(&state.Service{}), &grandpa.Service{}, diff --git a/dot/services.go b/dot/services.go index 9239cbcdae..5ee1e7e939 100644 --- a/dot/services.go +++ b/dot/services.go @@ -392,8 +392,8 @@ func (nodeBuilder) createSystemService(cfg *types.SystemInfo, stateSrvc *state.S } // createGRANDPAService creates a new GRANDPA service -func (nodeBuilder) createGRANDPAService(cfg *Config, st *state.Service, dh *digest.Handler, - ks keystore.Keystore, net *network.Service, telemetryMailer telemetry.Client) (*grandpa.Service, error) { +func (nodeBuilder) createGRANDPAService(cfg *Config, st *state.Service, ks keystore.Keystore, + net *network.Service, telemetryMailer telemetry.Client) (*grandpa.Service, error) { rt, err := st.Block.GetRuntime(nil) if err != nil { return nil, err diff --git a/dot/services_integration_test.go b/dot/services_integration_test.go index 157f493d0d..1a7252eda6 100644 --- a/dot/services_integration_test.go +++ b/dot/services_integration_test.go @@ -298,9 +298,6 @@ func TestCreateGrandpaService(t *testing.T) { err = builder.loadRuntime(cfg, ns, stateSrvc, ks, &network.Service{}) require.NoError(t, err) - dh, err := builder.createDigestHandler(cfg.Log.DigestLvl, stateSrvc) - require.NoError(t, err) - networkConfig := &network.Config{ BasePath: t.TempDir(), NoBootstrap: true, @@ -311,7 +308,7 @@ func TestCreateGrandpaService(t *testing.T) { testNetworkService, err := network.NewService(networkConfig) require.NoError(t, err) - gs, err := builder.createGRANDPAService(cfg, stateSrvc, dh, ks.Gran, testNetworkService, nil) + gs, err := builder.createGRANDPAService(cfg, stateSrvc, ks.Gran, testNetworkService, nil) require.NoError(t, err) require.NotNil(t, gs) } diff --git a/dot/services_test.go b/dot/services_test.go index e54e2ff053..a1e9feee38 100644 --- a/dot/services_test.go +++ b/dot/services_test.go @@ -449,7 +449,7 @@ func Test_nodeBuilder_createGRANDPAService(t *testing.T) { networkSrvc, err := network.NewService(networkConfig) require.NoError(t, err) builder := nodeBuilder{} - got, err := builder.createGRANDPAService(cfg, stateSrvc, nil, tt.ks, networkSrvc, + got, err := builder.createGRANDPAService(cfg, stateSrvc, tt.ks, networkSrvc, nil) assert.ErrorIs(t, err, tt.err) // TODO: create interface for grandpa.NewService to enable testing with assert.Equal From 9c2f8a1691f6ee607f252a7b1b6151db78ae563b Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Wed, 29 Jun 2022 15:18:48 -0400 Subject: [PATCH 91/93] chore: remove useless digest test --- dot/digest/digest_test.go | 90 --------------------------------------- 1 file changed, 90 deletions(-) diff --git a/dot/digest/digest_test.go b/dot/digest/digest_test.go index fe77a789d5..fa3414d42b 100644 --- a/dot/digest/digest_test.go +++ b/dot/digest/digest_test.go @@ -4,7 +4,6 @@ package digest import ( - "fmt" "testing" "time" @@ -439,95 +438,6 @@ func TestHandler_HandleNextConfigData(t *testing.T) { require.Equal(t, act.ToConfigData(), stored) } -func TestGrandpaScheduledChanges(t *testing.T) { - keyring, err := keystore.NewSr25519Keyring() - require.NoError(t, err) - - keyPairs := []*sr25519.Keypair{ - keyring.KeyAlice, keyring.KeyBob, keyring.KeyCharlie, - keyring.KeyDave, keyring.KeyEve, keyring.KeyFerdie, - keyring.KeyGeorge, keyring.KeyHeather, keyring.KeyIan, - } - - authorities := make([]types.AuthorityRaw, len(keyPairs)) - for i, keyPair := range keyPairs { - authorities[i] = types.AuthorityRaw{ - Key: keyPair.Public().(*sr25519.PublicKey).AsBytes(), - } - } - - forksGRANPDAScheduledChanges := []types.GrandpaScheduledChange{ - { - Auths: []types.GrandpaAuthoritiesRaw{ - { - Key: authorities[0].Key, - ID: 0, - }, - { - Key: authorities[1].Key, - ID: 1, - }, - }, - Delay: 1, - }, - { - Auths: []types.GrandpaAuthoritiesRaw{ - { - Key: authorities[3].Key, - ID: 3, - }, - { - Key: authorities[4].Key, - ID: 4, - }, - }, - Delay: 1, - }, - { - Auths: []types.GrandpaAuthoritiesRaw{ - { - Key: authorities[6].Key, - ID: 6, - }, - { - Key: authorities[7].Key, - ID: 7, - }, - }, - Delay: 1, - }, - } - - digestHandler, stateService := newTestHandler(t) - - genesisHeader, err := stateService.Block.BestBlockHeader() - require.NoError(t, err) - - forkA := issueBlocksWithGRANDPAScheduledChanges(t, keyring.KeyAlice, digestHandler, stateService, - genesisHeader, forksGRANPDAScheduledChanges[0], 2, 3) - forkB := issueBlocksWithGRANDPAScheduledChanges(t, keyring.KeyBob, digestHandler, stateService, - forkA[1], forksGRANPDAScheduledChanges[1], 1, 3) - forkC := issueBlocksWithGRANDPAScheduledChanges(t, keyring.KeyCharlie, digestHandler, stateService, - forkA[1], forksGRANPDAScheduledChanges[2], 1, 3) - - for _, fork := range forkA { - fmt.Printf("%d - %s\n", fork.Number, fork.Hash()) - } - - for _, fork := range forkB { - fmt.Printf("%d - %s\n", fork.Number, fork.Hash()) - } - - for _, fork := range forkC { - fmt.Printf("%d - %s\n", fork.Number, fork.Hash()) - } - - // current authorities at scheduled changes - //stateService.Block.AddBlock() - - //handler.handleConsensusDigest() -} - func issueBlocksWithGRANDPAScheduledChanges(t *testing.T, kp *sr25519.Keypair, dh *Handler, stateSvc *state.Service, parentHeader *types.Header, sc types.GrandpaScheduledChange, atBlock int, size int) (headers []*types.Header) { From f39ec36666600d587543b427be799f2e7ed195f2 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Wed, 29 Jun 2022 15:35:45 -0400 Subject: [PATCH 92/93] chore: fix mocks --- lib/grandpa/mocks_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/grandpa/mocks_test.go b/lib/grandpa/mocks_test.go index a91d5301de..97ba682105 100644 --- a/lib/grandpa/mocks_test.go +++ b/lib/grandpa/mocks_test.go @@ -495,6 +495,21 @@ func (mr *MockGrandpaStateMockRecorder) GetSetIDByBlockNumber(arg0 interface{}) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSetIDByBlockNumber", reflect.TypeOf((*MockGrandpaState)(nil).GetSetIDByBlockNumber), arg0) } +// NextGrandpaAuthorityChange mocks base method. +func (m *MockGrandpaState) NextGrandpaAuthorityChange(arg0 common.Hash, arg1 uint) (uint, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NextGrandpaAuthorityChange", arg0, arg1) + ret0, _ := ret[0].(uint) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NextGrandpaAuthorityChange indicates an expected call of NextGrandpaAuthorityChange. +func (mr *MockGrandpaStateMockRecorder) NextGrandpaAuthorityChange(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NextGrandpaAuthorityChange", reflect.TypeOf((*MockGrandpaState)(nil).NextGrandpaAuthorityChange), arg0, arg1) +} + // SetLatestRound mocks base method. func (m *MockGrandpaState) SetLatestRound(arg0 uint64) error { m.ctrl.T.Helper() From e81789fc80b4dfa967f5f493683d54c69006c7da Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Thu, 30 Jun 2022 10:24:45 -0400 Subject: [PATCH 93/93] chore: fix CI warns --- go.sum | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.sum b/go.sum index 2496b38ab9..7f686da259 100644 --- a/go.sum +++ b/go.sum @@ -897,6 +897,7 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= @@ -1516,6 +1517,7 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=