-
Notifications
You must be signed in to change notification settings - Fork 146
feat(lib/grandpa): Include equivocatory nodes while creating justification #1911
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
Changes from 41 commits
ef6546f
35fea30
1ceb49d
64b96fc
c2bf981
5039268
0100140
e2bafd8
b627509
d626dae
b0f3c27
7d21850
e6299b5
bbe8ba7
0ed4ce5
a5f4684
c9c278c
0274ef9
0b0c524
6e5dd0b
de8a4ed
f58afce
56f3d05
ad81602
c93a134
7abd69b
0301b49
ec61d8e
a7d9ecb
f1f4d04
5730645
69ee68d
5091650
349080b
32856fa
c10dc71
a0dd708
37b2ba3
add5820
e3546d8
9ac7015
3a4ee28
c675cab
9490f66
f3abef8
1bec0db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,6 +30,10 @@ var ( | |
| logger = log.NewFromGlobal(log.AddContext("pkg", "grandpa")) | ||
| ) | ||
|
|
||
| var ( | ||
| ErrUnsupportedSubround = errors.New("unsupported subround") | ||
| ) | ||
|
|
||
| // Service represents the current state of the grandpa protocol | ||
| type Service struct { | ||
| // preliminaries | ||
|
|
@@ -688,7 +692,7 @@ func (s *Service) determinePreCommit() (*Vote, error) { | |
| return &pvb, nil | ||
| } | ||
|
|
||
| // isFinalisable returns true is the round is finalisable, false otherwise. | ||
| // isFinalisable returns true if the round is finalisable, false otherwise. | ||
| func (s *Service) isFinalisable(round uint64) (bool, error) { | ||
| var pvb Vote | ||
| var err error | ||
|
|
@@ -806,16 +810,20 @@ func (s *Service) createJustification(bfc common.Hash, stage Subround) ([]Signed | |
| spc *sync.Map | ||
| err error | ||
| just []SignedVote | ||
| eqv map[ed25519.PublicKeyBytes][]*SignedVote | ||
| ) | ||
|
|
||
| switch stage { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should probably throw an error if you pass an unsupported
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added |
||
| case prevote: | ||
| spc = s.prevotes | ||
| eqv = s.pvEquivocations | ||
| case precommit: | ||
| spc = s.precommits | ||
| eqv = s.pcEquivocations | ||
| default: | ||
| return nil, fmt.Errorf("%w: %s", ErrUnsupportedSubround, stage) | ||
| } | ||
|
|
||
| // TODO: use equivacatory votes to create justification as well (#1667) | ||
| spc.Range(func(_, value interface{}) bool { | ||
| pc := value.(*SignedVote) | ||
| var isDescendant bool | ||
|
|
@@ -837,6 +845,12 @@ func (s *Service) createJustification(bfc common.Hash, stage Subround) ([]Signed | |
| return nil, err | ||
| } | ||
|
|
||
| for _, votes := range eqv { | ||
| for _, vote := range votes { | ||
| just = append(just, *vote) | ||
|
qdm12 marked this conversation as resolved.
|
||
| } | ||
|
noot marked this conversation as resolved.
|
||
| } | ||
|
|
||
| return just, nil | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -8,6 +8,7 @@ import ( | |||
| "math/big" | ||||
| "math/rand" | ||||
| "sort" | ||||
| "sync" | ||||
| "testing" | ||||
| "time" | ||||
|
|
||||
|
|
@@ -1264,3 +1265,158 @@ func TestFinalRoundGaugeMetric(t *testing.T) { | |||
| gauge := ethmetrics.GetOrRegisterGauge(finalityGrandpaRoundMetrics, nil) | ||||
| require.Equal(t, gauge.Value(), int64(180)) | ||||
| } | ||||
|
|
||||
| func TestGrandpaServiceCreateJustification_ShouldCountEquivocatoryVotes(t *testing.T) { | ||||
| // setup granpda service | ||||
| gs, st := newTestService(t) | ||||
| now := time.Unix(1000, 0) | ||||
|
|
||||
| const previousBlocksToAdd = 9 | ||||
| bfcBlock := addBlocksAndReturnTheLastOne(t, st.Block, previousBlocksToAdd, now) | ||||
|
|
||||
| bfcHash := bfcBlock.Header.Hash() | ||||
| bfcNumber := bfcBlock.Header.Number.Int64() | ||||
|
|
||||
| // create fake authorities | ||||
| ed25519Keyring, _ := keystore.NewEd25519Keyring() | ||||
|
kishansagathiya marked this conversation as resolved.
Outdated
|
||||
| fakeAuthorities := []*ed25519.Keypair{ | ||||
| ed25519Keyring.Alice().(*ed25519.Keypair), | ||||
| ed25519Keyring.Bob().(*ed25519.Keypair), | ||||
| ed25519Keyring.Charlie().(*ed25519.Keypair), | ||||
| ed25519Keyring.Dave().(*ed25519.Keypair), | ||||
| ed25519Keyring.Eve().(*ed25519.Keypair), | ||||
| ed25519Keyring.Bob().(*ed25519.Keypair), // equivocatory | ||||
| ed25519Keyring.Dave().(*ed25519.Keypair), // equivocatory | ||||
| } | ||||
|
|
||||
| equivocatories := make(map[ed25519.PublicKeyBytes][]*types.GrandpaSignedVote) | ||||
| prevotes := &sync.Map{} | ||||
|
|
||||
| var totalLegitVotes int | ||||
| // voting on | ||||
| for _, v := range fakeAuthorities { | ||||
| vote := &SignedVote{ | ||||
| AuthorityID: v.Public().(*ed25519.PublicKey).AsBytes(), | ||||
| Vote: types.GrandpaVote{ | ||||
| Hash: bfcHash, | ||||
| Number: uint32(bfcNumber), | ||||
| }, | ||||
| } | ||||
|
|
||||
| // to simulate the real world: | ||||
| // if the voter already has voted, then we remove | ||||
| // previous vote and add it on the equivocatories with the new vote | ||||
| previous, ok := prevotes.Load(vote.AuthorityID) | ||||
| if !ok { | ||||
| prevotes.Store(vote.AuthorityID, vote) | ||||
| totalLegitVotes++ | ||||
| } else { | ||||
| prevotes.Delete(vote.AuthorityID) | ||||
| equivocatories[vote.AuthorityID] = []*types.GrandpaSignedVote{ | ||||
| previous.(*types.GrandpaSignedVote), | ||||
| vote, | ||||
| } | ||||
| totalLegitVotes-- | ||||
| } | ||||
| } | ||||
|
|
||||
| gs.pvEquivocations = equivocatories | ||||
| gs.prevotes = prevotes | ||||
|
|
||||
| justifications, err := gs.createJustification(bfcHash, prevote) | ||||
| require.NoError(t, err) | ||||
|
|
||||
| var totalEqvVotes int | ||||
| // checks if the created justification contains all equivocatories votes | ||||
| for eqvPubKeyBytes, expectedVotes := range equivocatories { | ||||
| votesOnJustification := 0 | ||||
|
|
||||
| for _, justification := range justifications { | ||||
| if justification.AuthorityID == eqvPubKeyBytes { | ||||
| votesOnJustification++ | ||||
| } | ||||
| } | ||||
|
|
||||
| require.Equal(t, len(expectedVotes), votesOnJustification) | ||||
| totalEqvVotes += votesOnJustification | ||||
| } | ||||
|
|
||||
| require.Len(t, justifications, totalLegitVotes+totalEqvVotes) | ||||
| } | ||||
|
|
||||
| // addBlocksToState test helps adding previous blocks | ||||
| func addBlocksToState(t *testing.T, blockState *state.BlockState, depth int) { | ||||
| t.Helper() | ||||
|
|
||||
| previousHash := blockState.BestBlockHash() | ||||
|
|
||||
| rt, err := blockState.GetRuntime(nil) | ||||
| require.NoError(t, err) | ||||
|
|
||||
| head, err := blockState.BestBlockHeader() | ||||
| require.NoError(t, err) | ||||
|
|
||||
| startNum := int(head.Number.Int64()) | ||||
|
|
||||
| for i := startNum + 1; i <= depth; i++ { | ||||
| arrivalTime := time.Now() | ||||
|
|
||||
| d, err := types.NewBabePrimaryPreDigest(0, uint64(i), [32]byte{}, [64]byte{}).ToPreRuntimeDigest() | ||||
| require.NoError(t, err) | ||||
| require.NotNil(t, d) | ||||
| digest := types.NewDigest() | ||||
| err = digest.Add(*d) | ||||
| require.NoError(t, err) | ||||
|
|
||||
| block := &types.Block{ | ||||
| Header: types.Header{ | ||||
| ParentHash: previousHash, | ||||
| Number: big.NewInt(int64(i)), | ||||
| StateRoot: trie.EmptyHash, | ||||
| Digest: digest, | ||||
| }, | ||||
| Body: types.Body{}, | ||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit
Suggested change
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is required otherwise I will got
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shouldn't the error
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @EclesioMeloJunior just a small reminder 😉
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could you not check both? |
||||
| } | ||||
|
|
||||
| hash := block.Header.Hash() | ||||
| err = blockState.AddBlockWithArrivalTime(block, arrivalTime) | ||||
| require.NoError(t, err) | ||||
|
|
||||
| blockState.StoreRuntime(hash, rt) | ||||
| previousHash = hash | ||||
| } | ||||
| } | ||||
|
|
||||
| func addBlocksAndReturnTheLastOne(t *testing.T, blockState *state.BlockState, depth int, lastBlockArrivalTime time.Time) *types.Block { | ||||
| t.Helper() | ||||
| addBlocksToState(t, blockState, depth) | ||||
|
|
||||
| // create a new fake block to fake authorities commit on | ||||
| previousHash := blockState.BestBlockHash() | ||||
| previousHead, err := blockState.BestBlockHeader() | ||||
| require.NoError(t, err) | ||||
|
|
||||
| bfcNumber := int(previousHead.Number.Int64() + 1) | ||||
|
|
||||
| d, err := types.NewBabePrimaryPreDigest(0, uint64(bfcNumber), [32]byte{}, [64]byte{}).ToPreRuntimeDigest() | ||||
| require.NoError(t, err) | ||||
| require.NotNil(t, d) | ||||
| digest := types.NewDigest() | ||||
| err = digest.Add(*d) | ||||
| require.NoError(t, err) | ||||
|
|
||||
| bfcBlock := &types.Block{ | ||||
| Header: types.Header{ | ||||
| ParentHash: previousHash, | ||||
| Number: big.NewInt(int64(bfcNumber)), | ||||
| StateRoot: trie.EmptyHash, | ||||
| Digest: digest, | ||||
| }, | ||||
| Body: types.Body{}, | ||||
| } | ||||
|
|
||||
| err = blockState.AddBlockWithArrivalTime(bfcBlock, lastBlockArrivalTime) | ||||
| require.NoError(t, err) | ||||
|
|
||||
| return bfcBlock | ||||
| } | ||||
Uh oh!
There was an error while loading. Please reload this page.