Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

fix(state/grandpa): track changes across forks #2519

Merged
merged 108 commits into from
Jul 5, 2022
Merged
Show file tree
Hide file tree
Changes from 97 commits
Commits
Show all changes
108 commits
Select commit Hold shift + click to select a range
e193a0c
wip: adding grandpa consensus messages digest
EclesioMeloJunior Mar 28, 2022
edad574
wip: fork tree
EclesioMeloJunior Mar 29, 2022
300bb0c
Merge branch 'development' into eclesio/grandpa-changes-across-forks
EclesioMeloJunior Mar 29, 2022
5c9b3d0
chore: include grandpa state `ImportGrandpaChange` method
EclesioMeloJunior Mar 29, 2022
8d64eac
chore: wip using block tree
EclesioMeloJunior Apr 1, 2022
f0c8567
Merge branch 'development' into eclesio/grandpa-changes-across-forks
EclesioMeloJunior Apr 1, 2022
ed7407f
chore: implementing change across forks on scheduled changes
EclesioMeloJunior Apr 6, 2022
18bf1d7
fix: verify if next epoch already contains some definition
EclesioMeloJunior Apr 7, 2022
5ca05d0
chore: upgrade mockery version
EclesioMeloJunior Apr 7, 2022
44c3892
chore: remove duplicated code with generics
EclesioMeloJunior Apr 7, 2022
d69c694
chore: improve test comment
EclesioMeloJunior Apr 7, 2022
27bd2e8
chore: improve test comment
EclesioMeloJunior Apr 7, 2022
bbddab4
chore: improve test comment
EclesioMeloJunior Apr 7, 2022
71309cf
chore: improve test comment
EclesioMeloJunior Apr 7, 2022
58edb99
chore: remove the go:generate from the `EpochState`'s comment
EclesioMeloJunior Apr 7, 2022
47c8836
chore: use functions to define mocks in the test cases
EclesioMeloJunior Apr 8, 2022
a257349
chore: add more explanatory comments
EclesioMeloJunior Apr 8, 2022
088edcd
.
EclesioMeloJunior Apr 8, 2022
648b3ce
Merge branch 'eclesio/babe-check-already-persisted' into eclesio/gran…
EclesioMeloJunior Apr 8, 2022
c231439
wip: testing standard changes
EclesioMeloJunior Apr 12, 2022
931e8a0
chore: split into two different functions and check for existence bef…
EclesioMeloJunior Apr 12, 2022
73e3c33
chore: fix the ci lint warns
EclesioMeloJunior Apr 12, 2022
3906c65
wip
EclesioMeloJunior Apr 12, 2022
38ddcaa
chore: use a more descriptive name
EclesioMeloJunior Apr 12, 2022
27b016c
wip
EclesioMeloJunior Apr 13, 2022
dfd06d2
chore: unexport `Data` field on epoch_test.go
EclesioMeloJunior Apr 13, 2022
89a119f
chore: addressing last nits
EclesioMeloJunior Apr 13, 2022
f4cc36a
chore: rename to a better name
EclesioMeloJunior Apr 13, 2022
71ee157
chore: fix comments
EclesioMeloJunior Apr 13, 2022
cdd29ea
chore: addressing comments
EclesioMeloJunior Apr 13, 2022
d231f5a
Merge branch 'development' into eclesio/babe-check-already-persisted
EclesioMeloJunior Apr 13, 2022
16f35dc
chore: update mockery verison
EclesioMeloJunior Apr 13, 2022
1149c1f
chore: update devnet mocks as well
EclesioMeloJunior Apr 13, 2022
e9ad705
Merge branch 'eclesio/babe-check-already-persisted' into eclesio/gran…
EclesioMeloJunior Apr 13, 2022
7ce6a1c
chore: change name to be more explicit
EclesioMeloJunior Apr 14, 2022
4009824
wip
EclesioMeloJunior Apr 17, 2022
bf9315f
chore: wip
EclesioMeloJunior Apr 18, 2022
65d521a
Merge branch 'development' into eclesio/grandpa-changes-across-forks
EclesioMeloJunior Apr 18, 2022
869641c
chore: wip writing unit tests
EclesioMeloJunior Apr 24, 2022
d030667
chore: adding tests
EclesioMeloJunior Apr 25, 2022
a9dfa6b
chore: improve message
EclesioMeloJunior Apr 25, 2022
5fcc89a
chore: wip tersts
EclesioMeloJunior Apr 27, 2022
cd02908
chore: add unit tests for add scheduled changes to the fork tree
EclesioMeloJunior Apr 28, 2022
4eb6098
chore: added unit tests to apply_forced_changes
EclesioMeloJunior Apr 28, 2022
959ec96
chore: add tests to `getApplicableChange`
EclesioMeloJunior May 4, 2022
ca477fc
chore: resolve lint
EclesioMeloJunior May 4, 2022
b428a73
Merge branch 'development' into eclesio/grandpa-changes-across-forks
EclesioMeloJunior May 4, 2022
bfece81
chore: add logs to forced changes import and finalization
EclesioMeloJunior May 4, 2022
c17d759
upgrate deps again
jimjbrettj Apr 19, 2022
c92730f
wip: adding tests to forced changes digests
EclesioMeloJunior May 12, 2022
d6bf508
Merge branch 'development' into eclesio/grandpa-changes-across-forks
EclesioMeloJunior May 12, 2022
315445a
chore: ignoring scheduled changes in the presence of forced changes i…
EclesioMeloJunior May 12, 2022
e555241
chore: remove unneeded highest block verification while importing change
EclesioMeloJunior May 18, 2022
cbba07b
chore: fix unit tests
EclesioMeloJunior May 18, 2022
78f5b38
chore: update comment
EclesioMeloJunior May 19, 2022
4114c73
chore: include `BestFinalizedBlock` description
EclesioMeloJunior May 19, 2022
1f502b5
chore: adding a warn log to pause/resume grandpa handler
EclesioMeloJunior May 23, 2022
7b72104
chore: removing unneeded comments
EclesioMeloJunior May 23, 2022
82fa53e
chore: update recursive block creator helper func
EclesioMeloJunior May 23, 2022
3b04513
chore: removing `appliedBefore` change method
EclesioMeloJunior May 23, 2022
f57e49d
chore: unneeded block number comparision while pruning forced changes
EclesioMeloJunior May 23, 2022
2ee1967
chore: fix the logs and errors fmt
EclesioMeloJunior May 23, 2022
c46fa52
chore: encode consensus engine ID as hexadecimal
EclesioMeloJunior May 23, 2022
40b4294
chore: using `handleConsensusDigest` err context
EclesioMeloJunior May 23, 2022
8d1d17e
chore: improving comment on `ToConsensusDigests`
EclesioMeloJunior May 23, 2022
5989e30
chore: use a log to warn when we dont support a digest type
EclesioMeloJunior May 23, 2022
d6268f3
chore: add comment to `checkGRANDPAForcedChange`
EclesioMeloJunior May 23, 2022
1689570
chore: dont using aditional loops
EclesioMeloJunior May 23, 2022
b597b1d
chore: fix the lint
EclesioMeloJunior May 23, 2022
261db6d
wip: working on digests tests
EclesioMeloJunior May 23, 2022
977ee71
chore: make the change tracker more generic
EclesioMeloJunior May 31, 2022
6fb0d69
chore: not too generic
EclesioMeloJunior May 31, 2022
44ff71b
chore: fix lint warns
EclesioMeloJunior Jun 1, 2022
7057d5e
chore: fixing tests
EclesioMeloJunior Jun 1, 2022
af22fa7
chore: removing pause/resume unit tests
EclesioMeloJunior Jun 1, 2022
3981263
chore: fix NextGrandpaAuthorityChange rule
EclesioMeloJunior Jun 2, 2022
aef75e5
chore: fix the error assertion
EclesioMeloJunior Jun 2, 2022
36cc62c
chore: remove unecessary cast and lll
EclesioMeloJunior Jun 2, 2022
d5857bd
chore: fix typo
EclesioMeloJunior Jun 2, 2022
8664122
chore: fix typo
EclesioMeloJunior Jun 2, 2022
16f0bfc
chore: use binary search to insert a forced change into a ordered slice
EclesioMeloJunior Jun 2, 2022
814b230
Merge branch 'eclesio/grandpa-changes-across-forks' of github.com:Cha…
EclesioMeloJunior Jun 2, 2022
6cd04cd
core: match error name with errror description
EclesioMeloJunior Jun 2, 2022
8acc021
chore: fix error wrapper description
EclesioMeloJunior Jun 2, 2022
698d001
chore: improving the error wrapper description
EclesioMeloJunior Jun 2, 2022
0ef631e
chore: `HandleDigest` return error and log in `handleBlockImport`
EclesioMeloJunior Jun 2, 2022
f534e36
chore: remove a not needed return
EclesioMeloJunior Jun 2, 2022
575486a
chore: ensure the remaining changes belongs to the same chain as
EclesioMeloJunior Jun 2, 2022
e5ed08d
Merge branch 'development' into eclesio/grandpa-changes-across-forks
EclesioMeloJunior Jun 3, 2022
be0e9e5
chore: solve build failing
EclesioMeloJunior Jun 3, 2022
bd35350
chore: testing with the right wrapper
EclesioMeloJunior Jun 3, 2022
9fece6e
chore: resolving race conditions
EclesioMeloJunior Jun 3, 2022
cffb6ec
chore: fix race condition in unit tests
EclesioMeloJunior Jun 6, 2022
337758d
Merge branch 'development' into eclesio/grandpa-changes-across-forks
EclesioMeloJunior Jun 7, 2022
8e88f42
chore: addressing var changing name and wrapper errors
EclesioMeloJunior Jun 7, 2022
6ff15d3
chore: removing recursive error wrapper
EclesioMeloJunior Jun 7, 2022
6676efb
chore: avoiding shadowing
EclesioMeloJunior Jun 7, 2022
cac95b3
chore: fix typo
EclesioMeloJunior Jun 10, 2022
82adadb
chore: fix `add_scheduled_changes_with_same_hash` unit test
EclesioMeloJunior Jun 13, 2022
3302cd5
chore: add comments and check ancestry first
EclesioMeloJunior Jun 13, 2022
e901c3b
Merge branch 'development' into eclesio/grandpa-changes-across-forks
EclesioMeloJunior Jun 27, 2022
da4c66a
chore: remove `digest handler` from `createGRANDPAService`
EclesioMeloJunior Jun 27, 2022
9c2f8a1
chore: remove useless digest test
EclesioMeloJunior Jun 29, 2022
aecc299
Merge branch 'development' into eclesio/grandpa-changes-across-forks
EclesioMeloJunior Jun 29, 2022
f39ec36
chore: fix mocks
EclesioMeloJunior Jun 29, 2022
8d224f6
Merge branch 'development' into eclesio/grandpa-changes-across-forks
EclesioMeloJunior Jun 30, 2022
e81789f
chore: fix CI warns
EclesioMeloJunior Jun 30, 2022
fbf675f
Merge branch 'development' into eclesio/grandpa-changes-across-forks
EclesioMeloJunior Jul 4, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions dot/core/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
302 changes: 79 additions & 223 deletions dot/digest/digest.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ var (
_ services.Service = &Handler{}
)

var (
ErrUnknownConsensusEngineID = 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
Expand All @@ -32,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) {
Expand Down Expand Up @@ -91,44 +76,80 @@ 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
// HandleDigests handles consensus digests for an imported block
func (h *Handler) HandleDigests(header *types.Header) error {
consensusDigests := h.toConsensusDigests(header.Digest.Types)
consensusDigests, err := checkForGRANDPAForcedChanges(consensusDigests)
if err != nil {
return fmt.Errorf("failed while checking GRANDPA digests: %w", err)
}

if h.grandpaForcedChange != nil && h.grandpaForcedChange.atBlock < next {
next = h.grandpaForcedChange.atBlock
for i := range consensusDigests {
// avoiding implicit memory aliasing in for loop, since:
// for _, digest := range consensusDigests { &digest }
// is using the address of a loop variable
EclesioMeloJunior marked this conversation as resolved.
Show resolved Hide resolved
digest := consensusDigests[i]
EclesioMeloJunior marked this conversation as resolved.
Show resolved Hide resolved
err := h.handleConsensusDigest(&digest, header)
if err != nil {
h.logger.Errorf("cannot handle consensus digest: %w", err)
}
}

if h.grandpaPause != nil && h.grandpaPause.atBlock < next {
next = h.grandpaPause.atBlock
}
return nil
}

// 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
EclesioMeloJunior marked this conversation as resolved.
Show resolved Hide resolved
}

if h.grandpaResume != nil && h.grandpaResume.atBlock < next {
next = h.grandpaResume.atBlock
switch digest.ConsensusEngineID {
case types.GrandpaEngineID, types.BabeEngineID:
consensusDigests = append(consensusDigests, digest)
}
}

return next
return consensusDigests
}

// 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)
if !ok {
// 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) {
EclesioMeloJunior marked this conversation as resolved.
Show resolved Hide resolved
EclesioMeloJunior marked this conversation as resolved.
Show resolved Hide resolved
var hasForcedChange bool
digestsWithoutScheduled := make([]types.ConsensusDigest, 0, len(digests))
EclesioMeloJunior marked this conversation as resolved.
Show resolved Hide resolved
for _, digest := range digests {
if digest.ConsensusEngineID != types.GrandpaEngineID {
EclesioMeloJunior marked this conversation as resolved.
Show resolved Hide resolved
digestsWithoutScheduled = append(digestsWithoutScheduled, digest)
continue
}

err := h.handleConsensusDigest(&val, header)
data := types.NewGrandpaConsensusDigest()
err := scale.Unmarshal(digest.Data, &data)
if err != nil {
h.logger.Errorf("cannot handle digest for block number %d, index %d, digest %s: %s",
header.Number, i, d.Value(), err)
return nil, fmt.Errorf("cannot unmarshal GRANDPA consensus digest: %w", err)
}

switch data.Value().(type) {
case types.GrandpaScheduledChange:
case types.GrandpaForcedChange:
hasForcedChange = true
digestsWithoutScheduled = append(digestsWithoutScheduled, digest)
default:
digestsWithoutScheduled = append(digestsWithoutScheduled, digest)
qdm12 marked this conversation as resolved.
Show resolved Hide resolved
}
}

if hasForcedChange {
return digestsWithoutScheduled, nil
}

return digests, nil
}

func (h *Handler) handleConsensusDigest(d *types.ConsensusDigest, header *types.Header) error {
Expand All @@ -139,42 +160,19 @@ 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.HandleGRANDPADigest(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 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)
EclesioMeloJunior marked this conversation as resolved.
Show resolved Hide resolved
return h.handleBabeConsensusDigest(data, header)
default:
return fmt.Errorf("%w: 0x%x", ErrUnknownConsensusEngineID, d.ConsensusEngineID.ToBytes())
}

return errors.New("invalid consensus digest data")
}

func (h *Handler) handleBabeConsensusDigest(digest scale.VaryingDataType, header *types.Header) error {
Expand All @@ -194,7 +192,7 @@ func (h *Handler) handleBabeConsensusDigest(digest scale.VaryingDataType, header
return nil

case types.BABEOnDisabled:
return h.handleBABEOnDisabled(val, header)
return nil

case types.NextConfigData:
currEpoch, err := h.epochState.GetEpochForBlock(header)
Expand All @@ -220,10 +218,14 @@ func (h *Handler) handleBlockImport(ctx context.Context) {
continue
}

h.HandleDigests(&block.Header)
err := h.handleGrandpaChangesOnImport(block.Header.Number)
err := h.HandleDigests(&block.Header)
if err != nil {
h.logger.Errorf("failed to handle grandpa changes on block import: %s", err)
h.logger.Errorf("failed to handle digests: %s", err)
}

err = h.grandpaState.ApplyForcedChanges(&block.Header)
if err != nil {
h.logger.Errorf("failed to apply forced changes: %s", err)
}
case <-ctx.Done():
return
Expand All @@ -249,159 +251,13 @@ func (h *Handler) handleBlockFinalisation(ctx context.Context) {
h.logger.Errorf("failed to persist babe next epoch config: %s", err)
}

err = h.handleGrandpaChangesOnFinalization(info.Header.Number)
err = h.grandpaState.ApplyScheduledChanges(&info.Header)
if err != nil {
h.logger.Errorf("failed to handle grandpa changes on block finalisation: %s", err)
h.logger.Errorf("failed to apply scheduled change: %s", err)
}

case <-ctx.Done():
return
}
}
}

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) 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 {
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)
}

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
}
Loading