Skip to content

Commit af7725f

Browse files
EclesioMeloJuniortimwu20
authored andcommitted
feat(lib/grandpa): Include equivocatory nodes while creating justification (ChainSafe#1911)
* feat: include equivocatory nodes while creating just * chore: remove unecessary error return * chore: undo condition format * chore: use equivocatory votes on threshold count * chore: add unsupported subround error * chore: remove unecessary check * chore: remove eqv votes and use the eqv voters * chore: remove unecessary convertion * chore: export ErrUnsupportedSubround * chore: improve code reading * chore: add tests to grandpa.createJustification func * chore: add tests to grandpa message handler files * chore: fix ci warns * chore: put back a condition to check enough precommits in the justification * chore: check if the equivocatory votes are on justification * chore: fix the fakeAuthorities format * chore: removing parallel and uncoment Err... * chore: adjusting the equivocatory and prevote testing * chore: fix lint warns * chore: address nit comments * chore: adjust test asserts * chore: adjust naming * chore: mock time.Unix in tests * chore: add test to equivocatory voters on to VerifyBlockJustification * chore: adjust testing * chore: check errors * chore: changing the style to checking ok variable * chore: fix conflicts and ignore testing lint warns * chore: check err * chore: fix runtime not found problems * chore: removing unused imports
1 parent eca12c3 commit af7725f

File tree

6 files changed

+599
-22
lines changed

6 files changed

+599
-22
lines changed

.golangci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,10 @@ issues:
157157
source: "^//go:generate "
158158
text: "long-lines"
159159

160+
- text: 'G204: Subprocess launched with variable'
161+
linters:
162+
- gosec
163+
160164
# Independently from option `exclude` we use default exclude patterns,
161165
# it can be disabled by this option. To list all
162166
# excluded by default patterns execute `golangci-lint run --help`.

lib/grandpa/grandpa.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ var (
3030
logger = log.NewFromGlobal(log.AddContext("pkg", "grandpa"))
3131
)
3232

33+
var (
34+
ErrUnsupportedSubround = errors.New("unsupported subround")
35+
)
36+
3337
// Service represents the current state of the grandpa protocol
3438
type Service struct {
3539
// preliminaries
@@ -688,7 +692,7 @@ func (s *Service) determinePreCommit() (*Vote, error) {
688692
return &pvb, nil
689693
}
690694

691-
// isFinalisable returns true is the round is finalisable, false otherwise.
695+
// isFinalisable returns true if the round is finalisable, false otherwise.
692696
func (s *Service) isFinalisable(round uint64) (bool, error) {
693697
var pvb Vote
694698
var err error
@@ -806,16 +810,20 @@ func (s *Service) createJustification(bfc common.Hash, stage Subround) ([]Signed
806810
spc *sync.Map
807811
err error
808812
just []SignedVote
813+
eqv map[ed25519.PublicKeyBytes][]*SignedVote
809814
)
810815

811816
switch stage {
812817
case prevote:
813818
spc = s.prevotes
819+
eqv = s.pvEquivocations
814820
case precommit:
815821
spc = s.precommits
822+
eqv = s.pcEquivocations
823+
default:
824+
return nil, fmt.Errorf("%w: %s", ErrUnsupportedSubround, stage)
816825
}
817826

818-
// TODO: use equivacatory votes to create justification as well (#1667)
819827
spc.Range(func(_, value interface{}) bool {
820828
pc := value.(*SignedVote)
821829
var isDescendant bool
@@ -837,6 +845,12 @@ func (s *Service) createJustification(bfc common.Hash, stage Subround) ([]Signed
837845
return nil, err
838846
}
839847

848+
for _, votes := range eqv {
849+
for _, vote := range votes {
850+
just = append(just, *vote)
851+
}
852+
}
853+
840854
return just, nil
841855
}
842856

lib/grandpa/grandpa_test.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"math/big"
99
"math/rand"
1010
"sort"
11+
"sync"
1112
"testing"
1213
"time"
1314

@@ -1264,3 +1265,159 @@ func TestFinalRoundGaugeMetric(t *testing.T) {
12641265
gauge := ethmetrics.GetOrRegisterGauge(finalityGrandpaRoundMetrics, nil)
12651266
require.Equal(t, gauge.Value(), int64(180))
12661267
}
1268+
1269+
func TestGrandpaServiceCreateJustification_ShouldCountEquivocatoryVotes(t *testing.T) {
1270+
// setup granpda service
1271+
gs, st := newTestService(t)
1272+
now := time.Unix(1000, 0)
1273+
1274+
const previousBlocksToAdd = 9
1275+
bfcBlock := addBlocksAndReturnTheLastOne(t, st.Block, previousBlocksToAdd, now)
1276+
1277+
bfcHash := bfcBlock.Header.Hash()
1278+
bfcNumber := bfcBlock.Header.Number.Int64()
1279+
1280+
// create fake authorities
1281+
ed25519Keyring, err := keystore.NewEd25519Keyring()
1282+
require.NoError(t, err)
1283+
fakeAuthorities := []*ed25519.Keypair{
1284+
ed25519Keyring.Alice().(*ed25519.Keypair),
1285+
ed25519Keyring.Bob().(*ed25519.Keypair),
1286+
ed25519Keyring.Charlie().(*ed25519.Keypair),
1287+
ed25519Keyring.Dave().(*ed25519.Keypair),
1288+
ed25519Keyring.Eve().(*ed25519.Keypair),
1289+
ed25519Keyring.Bob().(*ed25519.Keypair), // equivocatory
1290+
ed25519Keyring.Dave().(*ed25519.Keypair), // equivocatory
1291+
}
1292+
1293+
equivocatories := make(map[ed25519.PublicKeyBytes][]*types.GrandpaSignedVote)
1294+
prevotes := &sync.Map{}
1295+
1296+
var totalLegitVotes int
1297+
// voting on
1298+
for _, v := range fakeAuthorities {
1299+
vote := &SignedVote{
1300+
AuthorityID: v.Public().(*ed25519.PublicKey).AsBytes(),
1301+
Vote: types.GrandpaVote{
1302+
Hash: bfcHash,
1303+
Number: uint32(bfcNumber),
1304+
},
1305+
}
1306+
1307+
// to simulate the real world:
1308+
// if the voter already has voted, then we remove
1309+
// previous vote and add it on the equivocatories with the new vote
1310+
previous, ok := prevotes.Load(vote.AuthorityID)
1311+
if !ok {
1312+
prevotes.Store(vote.AuthorityID, vote)
1313+
totalLegitVotes++
1314+
} else {
1315+
prevotes.Delete(vote.AuthorityID)
1316+
equivocatories[vote.AuthorityID] = []*types.GrandpaSignedVote{
1317+
previous.(*types.GrandpaSignedVote),
1318+
vote,
1319+
}
1320+
totalLegitVotes--
1321+
}
1322+
}
1323+
1324+
gs.pvEquivocations = equivocatories
1325+
gs.prevotes = prevotes
1326+
1327+
justifications, err := gs.createJustification(bfcHash, prevote)
1328+
require.NoError(t, err)
1329+
1330+
var totalEqvVotes int
1331+
// checks if the created justification contains all equivocatories votes
1332+
for eqvPubKeyBytes, expectedVotes := range equivocatories {
1333+
votesOnJustification := 0
1334+
1335+
for _, justification := range justifications {
1336+
if justification.AuthorityID == eqvPubKeyBytes {
1337+
votesOnJustification++
1338+
}
1339+
}
1340+
1341+
require.Equal(t, len(expectedVotes), votesOnJustification)
1342+
totalEqvVotes += votesOnJustification
1343+
}
1344+
1345+
require.Len(t, justifications, totalLegitVotes+totalEqvVotes)
1346+
}
1347+
1348+
// addBlocksToState test helps adding previous blocks
1349+
func addBlocksToState(t *testing.T, blockState *state.BlockState, depth int) {
1350+
t.Helper()
1351+
1352+
previousHash := blockState.BestBlockHash()
1353+
1354+
rt, err := blockState.GetRuntime(nil)
1355+
require.NoError(t, err)
1356+
1357+
head, err := blockState.BestBlockHeader()
1358+
require.NoError(t, err)
1359+
1360+
startNum := int(head.Number.Int64())
1361+
1362+
for i := startNum + 1; i <= depth; i++ {
1363+
arrivalTime := time.Now()
1364+
1365+
d, err := types.NewBabePrimaryPreDigest(0, uint64(i), [32]byte{}, [64]byte{}).ToPreRuntimeDigest()
1366+
require.NoError(t, err)
1367+
require.NotNil(t, d)
1368+
digest := types.NewDigest()
1369+
err = digest.Add(*d)
1370+
require.NoError(t, err)
1371+
1372+
block := &types.Block{
1373+
Header: types.Header{
1374+
ParentHash: previousHash,
1375+
Number: big.NewInt(int64(i)),
1376+
StateRoot: trie.EmptyHash,
1377+
Digest: digest,
1378+
},
1379+
Body: types.Body{},
1380+
}
1381+
1382+
hash := block.Header.Hash()
1383+
err = blockState.AddBlockWithArrivalTime(block, arrivalTime)
1384+
require.NoError(t, err)
1385+
1386+
blockState.StoreRuntime(hash, rt)
1387+
previousHash = hash
1388+
}
1389+
}
1390+
1391+
func addBlocksAndReturnTheLastOne(t *testing.T, blockState *state.BlockState, depth int, lastBlockArrivalTime time.Time) *types.Block {
1392+
t.Helper()
1393+
addBlocksToState(t, blockState, depth)
1394+
1395+
// create a new fake block to fake authorities commit on
1396+
previousHash := blockState.BestBlockHash()
1397+
previousHead, err := blockState.BestBlockHeader()
1398+
require.NoError(t, err)
1399+
1400+
bfcNumber := int(previousHead.Number.Int64() + 1)
1401+
1402+
d, err := types.NewBabePrimaryPreDigest(0, uint64(bfcNumber), [32]byte{}, [64]byte{}).ToPreRuntimeDigest()
1403+
require.NoError(t, err)
1404+
require.NotNil(t, d)
1405+
digest := types.NewDigest()
1406+
err = digest.Add(*d)
1407+
require.NoError(t, err)
1408+
1409+
bfcBlock := &types.Block{
1410+
Header: types.Header{
1411+
ParentHash: previousHash,
1412+
Number: big.NewInt(int64(bfcNumber)),
1413+
StateRoot: trie.EmptyHash,
1414+
Digest: digest,
1415+
},
1416+
Body: types.Body{},
1417+
}
1418+
1419+
err = blockState.AddBlockWithArrivalTime(bfcBlock, lastBlockArrivalTime)
1420+
require.NoError(t, err)
1421+
1422+
return bfcBlock
1423+
}

0 commit comments

Comments
 (0)