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

feat(lib/grandpa): Include equivocatory nodes while creating justification #1911

Merged
merged 46 commits into from
Nov 24, 2021
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
ef6546f
feat: include equivocatory nodes while creating just
EclesioMeloJunior Oct 20, 2021
35fea30
chore: remove unecessary error return
EclesioMeloJunior Oct 20, 2021
1ceb49d
Merge branch 'development' into eclesio/include-eqv-votes
EclesioMeloJunior Oct 22, 2021
64b96fc
chore: undo condition format
EclesioMeloJunior Oct 22, 2021
c2bf981
chore: use equivocatory votes on threshold count
EclesioMeloJunior Oct 26, 2021
5039268
chore: add unsupported subround error
EclesioMeloJunior Oct 26, 2021
0100140
Merge branch 'development' into eclesio/include-eqv-votes
EclesioMeloJunior Oct 26, 2021
e2bafd8
Merge branch 'development' into eclesio/include-eqv-votes
EclesioMeloJunior Nov 1, 2021
b627509
Merge branch 'development' into eclesio/include-eqv-votes
EclesioMeloJunior Nov 2, 2021
d626dae
chore: remove unecessary check
EclesioMeloJunior Nov 2, 2021
b0f3c27
Merge branch 'development' into eclesio/include-eqv-votes
EclesioMeloJunior Nov 3, 2021
7d21850
Merge branch 'development' into eclesio/include-eqv-votes
EclesioMeloJunior Nov 3, 2021
e6299b5
chore: remove eqv votes and use the eqv voters
EclesioMeloJunior Nov 3, 2021
bbe8ba7
Merge branch 'eclesio/include-eqv-votes' of github.com:ChainSafe/goss…
EclesioMeloJunior Nov 3, 2021
0ed4ce5
Merge branch 'development' into eclesio/include-eqv-votes
EclesioMeloJunior Nov 3, 2021
a5f4684
chore: remove unecessary convertion
EclesioMeloJunior Nov 3, 2021
c9c278c
Merge branch 'eclesio/include-eqv-votes' of github.com:ChainSafe/goss…
EclesioMeloJunior Nov 3, 2021
0274ef9
chore: export ErrUnsupportedSubround
EclesioMeloJunior Nov 3, 2021
0b0c524
chore: improve code reading
EclesioMeloJunior Nov 4, 2021
6e5dd0b
Merge branch 'development' into eclesio/include-eqv-votes
EclesioMeloJunior Nov 4, 2021
de8a4ed
Merge branch 'development' into eclesio/include-eqv-votes
EclesioMeloJunior Nov 11, 2021
f58afce
chore: add tests to grandpa.createJustification func
EclesioMeloJunior Nov 15, 2021
56f3d05
Merge branch 'development' into eclesio/include-eqv-votes
EclesioMeloJunior Nov 15, 2021
ad81602
chore: add tests to grandpa message handler files
EclesioMeloJunior Nov 15, 2021
c93a134
chore: fix ci warns
EclesioMeloJunior Nov 16, 2021
7abd69b
Merge branch 'development' into eclesio/include-eqv-votes
EclesioMeloJunior Nov 16, 2021
0301b49
chore: put back a condition to check enough precommits in the justifi…
EclesioMeloJunior Nov 17, 2021
ec61d8e
chore: check if the equivocatory votes are on justification
EclesioMeloJunior Nov 17, 2021
a7d9ecb
chore: fix the fakeAuthorities format
EclesioMeloJunior Nov 17, 2021
f1f4d04
chore: removing parallel and uncoment Err...
EclesioMeloJunior Nov 17, 2021
5730645
chore: adjusting the equivocatory and prevote testing
EclesioMeloJunior Nov 17, 2021
69ee68d
chore: fix lint warns
EclesioMeloJunior Nov 17, 2021
5091650
chore: address nit comments
EclesioMeloJunior Nov 19, 2021
349080b
chore: adjust test asserts
EclesioMeloJunior Nov 19, 2021
32856fa
chore: adjust naming
EclesioMeloJunior Nov 19, 2021
c10dc71
chore: mock time.Unix in tests
EclesioMeloJunior Nov 19, 2021
a0dd708
chore: add test to equivocatory voters on to VerifyBlockJustification
EclesioMeloJunior Nov 19, 2021
37b2ba3
Merge branch 'development' into eclesio/include-eqv-votes
EclesioMeloJunior Nov 22, 2021
add5820
chore: adjust testing
EclesioMeloJunior Nov 22, 2021
e3546d8
chore: check errors
EclesioMeloJunior Nov 23, 2021
9ac7015
chore: changing the style to checking ok variable
EclesioMeloJunior Nov 23, 2021
3a4ee28
Merge branch 'development' into eclesio/include-eqv-votes
EclesioMeloJunior Nov 23, 2021
c675cab
chore: fix conflicts and ignore testing lint warns
EclesioMeloJunior Nov 23, 2021
9490f66
chore: check err
EclesioMeloJunior Nov 23, 2021
f3abef8
chore: fix runtime not found problems
EclesioMeloJunior Nov 24, 2021
1bec0db
chore: removing unused imports
EclesioMeloJunior Nov 24, 2021
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
18 changes: 16 additions & 2 deletions lib/grandpa/grandpa.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ var (
logger = log.NewFromGlobal(log.AddContext("pkg", "grandpa"))
)

var (
ErrUnsupportedSubround = errors.New("unsupported subround")
qdm12 marked this conversation as resolved.
Show resolved Hide resolved
)

// Service represents the current state of the grandpa protocol
type Service struct {
// preliminaries
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should probably throw an error if you pass an unsupported Subround.

Copy link
Member Author

Choose a reason for hiding this comment

The 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
Expand All @@ -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.
Show resolved Hide resolved
}
}

return just, nil
}

Expand Down
156 changes: 156 additions & 0 deletions lib/grandpa/grandpa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"math/big"
"math/rand"
"sort"
"sync"
"testing"
"time"

Expand Down Expand Up @@ -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.
Show resolved Hide resolved
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{},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit

Suggested change
Body: types.Body{},

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is required otherwise I will got empty block body, this is why Body is []byte so if I don't pass the default value will be nil afaik

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't the error empty block body be returned even for a zero length body? 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@EclesioMeloJunior just a small reminder 😉

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@qdm12, sorry for the delay, the right error is block body is nil, @noot in the function AddBlockWithArrivalTime, can I change the check block.Body == nil to len(block.Body) == 0?

Copy link
Contributor

Choose a reason for hiding this comment

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