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) add round state rpc call #1664

Merged
merged 28 commits into from
Jul 9, 2021
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
fdac471
chore: add interface for grandpa in rpc pkg
EclesioMeloJunior Jun 29, 2021
888de1f
chore: create roundState rpc call
EclesioMeloJunior Jun 29, 2021
157d3fd
chore: coment unused branch
EclesioMeloJunior Jun 29, 2021
904dbde
chore: fix lint
EclesioMeloJunior Jun 29, 2021
fc77c6d
Merge branch 'development' into eclesio/grandpa-roundState-rpc
EclesioMeloJunior Jun 29, 2021
feefdb2
Merge branch 'development' into eclesio/grandpa-roundState-rpc
EclesioMeloJunior Jun 30, 2021
6fad347
chore: add test case
EclesioMeloJunior Jul 1, 2021
3b41f90
Merge branch 'eclesio/grandpa-roundState-rpc' of github.com:ChainSafe…
EclesioMeloJunior Jul 1, 2021
ff811b1
chore: fix lint
EclesioMeloJunior Jul 1, 2021
4a507fc
Merge branch 'development' into eclesio/grandpa-roundState-rpc
EclesioMeloJunior Jul 2, 2021
d41c6cc
Merge branch 'development' into eclesio/grandpa-roundState-rpc
EclesioMeloJunior Jul 2, 2021
3802e97
Merge branch 'development' into eclesio/grandpa-roundState-rpc
EclesioMeloJunior Jul 5, 2021
111425e
chore: address comments
EclesioMeloJunior Jul 5, 2021
66ac354
Merge branch 'development' into eclesio/grandpa-roundState-rpc
EclesioMeloJunior Jul 5, 2021
2de3648
Merge branch 'development' into eclesio/grandpa-roundState-rpc
EclesioMeloJunior Jul 6, 2021
24c3e7b
chore: fix the diff implementation
EclesioMeloJunior Jul 6, 2021
04a3673
Merge branch 'eclesio/grandpa-roundState-rpc' of github.com:ChainSafe…
EclesioMeloJunior Jul 6, 2021
917f35c
chore: address comment
EclesioMeloJunior Jul 6, 2021
1898b3e
chore: fix conflicts
EclesioMeloJunior Jul 7, 2021
41039c2
Merge branch 'development' into eclesio/grandpa-roundState-rpc
EclesioMeloJunior Jul 7, 2021
f2d0fb9
chore: change PreVotes and PreCommits interface signature
EclesioMeloJunior Jul 7, 2021
01c18a5
Merge branch 'development' into eclesio/grandpa-roundState-rpc
EclesioMeloJunior Jul 7, 2021
662c418
chore: remove check
EclesioMeloJunior Jul 7, 2021
c5c3052
Merge branch 'eclesio/grandpa-roundState-rpc' of github.com:ChainSafe…
EclesioMeloJunior Jul 7, 2021
91ed22b
Merge branch 'development' into eclesio/grandpa-roundState-rpc
EclesioMeloJunior Jul 8, 2021
205277c
Merge branch 'development' into eclesio/grandpa-roundState-rpc
EclesioMeloJunior Jul 8, 2021
0338913
chore: improve code defs
EclesioMeloJunior Jul 8, 2021
32bbf8a
Merge branch 'development' into eclesio/grandpa-roundState-rpc
EclesioMeloJunior Jul 9, 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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ endif
@echo "> Generating mocks at $(path)"

ifeq ($(INMOCKS),1)
cd $(path); $(GOPATH)/bin/mockery --name $(interface) --inpackage --keeptree --case underscore
cd $(path); $(GOPATH)/bin/mockery --name $(interface) --structname Mock$(interface) --case underscore --keeptree
else
$(GOPATH)/bin/mockery --srcpkg $(path) --name $(interface) --case underscore --inpackage
endif
Expand Down
2 changes: 1 addition & 1 deletion dot/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ func NewNode(cfg *Config, ks *keystore.GlobalKeystore, stopFunc func()) (*Node,

// check if rpc service is enabled
if enabled := cfg.RPC.Enabled || cfg.RPC.WS; enabled {
rpcSrvc := createRPCService(cfg, stateSrvc, coreSrvc, networkSrvc, bp, rt, sysSrvc)
rpcSrvc := createRPCService(cfg, stateSrvc, coreSrvc, networkSrvc, bp, rt, sysSrvc, fg)
nodeSrvcs = append(nodeSrvcs, rpcSrvc)
} else {
logger.Debug("rpc service disabled by default", "rpc", enabled)
Expand Down
3 changes: 2 additions & 1 deletion dot/rpc/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type HTTPServerConfig struct {
NetworkAPI modules.NetworkAPI
CoreAPI modules.CoreAPI
BlockProducerAPI modules.BlockProducerAPI
BlockFinalityAPI modules.BlockFinalityAPI
RuntimeAPI modules.RuntimeAPI
TransactionQueueAPI modules.TransactionStateAPI
RPCAPI modules.RPCAPI
Expand Down Expand Up @@ -99,7 +100,7 @@ func (h *HTTPServer) RegisterModules(mods []string) {
case "chain":
srvc = modules.NewChainModule(h.serverConfig.BlockAPI)
case "grandpa":
srvc = modules.NewGrandpaModule(h.serverConfig.BlockAPI)
srvc = modules.NewGrandpaModule(h.serverConfig.BlockAPI, h.serverConfig.BlockFinalityAPI)
case "state":
srvc = modules.NewStateModule(h.serverConfig.NetworkAPI, h.serverConfig.StorageAPI, h.serverConfig.CoreAPI)
case "rpc":
Expand Down
11 changes: 11 additions & 0 deletions dot/rpc/modules/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"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/grandpa"
"github.com/ChainSafe/gossamer/lib/runtime"
"github.com/ChainSafe/gossamer/lib/transaction"
)
Expand Down Expand Up @@ -93,3 +95,12 @@ type SystemAPI interface {
ChainType() string
ChainName() string
}

// BlockFinalityAPI is the interface for handling block finalisation methods
type BlockFinalityAPI interface {
GetSetID() uint64
GetRound() uint64
GetVoters() grandpa.Voters
PreVotes() ([]ed25519.PublicKeyBytes, []ed25519.PublicKeyBytes)
PreCommits() ([]ed25519.PublicKeyBytes, []ed25519.PublicKeyBytes)
}
112 changes: 109 additions & 3 deletions dot/rpc/modules/grandpa.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,45 @@ import (
"net/http"

"github.com/ChainSafe/gossamer/lib/common"
"github.com/ChainSafe/gossamer/lib/crypto/ed25519"
)

// GrandpaModule init parameters
type GrandpaModule struct {
blockAPI BlockAPI
blockAPI BlockAPI
blockFinalityAPI BlockFinalityAPI
}

// NewGrandpaModule creates a new Grandpa rpc module.
func NewGrandpaModule(api BlockAPI) *GrandpaModule {
func NewGrandpaModule(api BlockAPI, finalityAPI BlockFinalityAPI) *GrandpaModule {
return &GrandpaModule{
blockAPI: api,
blockAPI: api,
blockFinalityAPI: finalityAPI,
}
}

// Votes struct formats rpc call
type Votes struct {
CurrentWeight uint32 `json:"currentWeight"`
Missing []string `json:"missing"`
}

// RoundState json format for roundState RPC call
type RoundState struct {
Round uint32 `json:"round"`
TotalWeight uint32 `json:"totalWeight"`
ThresholdWeight uint32 `json:"thresholdWeight"`
Prevotes Votes `json:"prevotes"`
Precommits Votes `json:"precommits"`
}

// RoundStateResponse response to roundState RPC call
type RoundStateResponse struct {
SetID uint32 `json:"setId"`
Best RoundState `json:"best"`
Background []RoundState `json:"background"`
}

// ProveFinalityRequest request struct
type ProveFinalityRequest struct {
blockHashStart common.Hash
Expand Down Expand Up @@ -71,3 +96,84 @@ func (gm *GrandpaModule) ProveFinality(r *http.Request, req *ProveFinalityReques

return nil
}

// RoundState returns the state of the current best round state as well as the ongoing background rounds.
func (gm *GrandpaModule) RoundState(r *http.Request, req *EmptyRequest, res *RoundStateResponse) error {
voters := gm.blockFinalityAPI.GetVoters()
votersPkBytes := make([]ed25519.PublicKeyBytes, len(voters))
for i, v := range voters {
votersPkBytes[i] = v.PublicKeyBytes()
}

prevotes, pvEquivocations := gm.blockFinalityAPI.PreVotes()
precommits, pcEquivocations := gm.blockFinalityAPI.PreCommits()

missingPrevotes, err := toAddress(difference(votersPkBytes, prevotes))
if err != nil {
return err
}

missingPrecommits, err := toAddress(difference(votersPkBytes, precommits))
if err != nil {
return err
}
Copy link
Contributor

Choose a reason for hiding this comment

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

equivocations should be counted as votes, since difference returns authorities that haven't voted. I'd suggest updating PreVotes() and PreCommits() to return just one list []ed25519.PublicKeyBytes that contains all the voters that are in both

Copy link
Member Author

Choose a reason for hiding this comment

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

Done!


totalWeight := uint32(len(voters))
roundstate := RoundStateResponse{
SetID: uint32(gm.blockFinalityAPI.GetSetID()),
Best: RoundState{
Round: uint32(gm.blockFinalityAPI.GetRound()),
ThresholdWeight: thresholdWeight(totalWeight),
TotalWeight: totalWeight,
Prevotes: Votes{
CurrentWeight: uint32(len(prevotes) + len(pvEquivocations)),
Missing: missingPrevotes,
},
Precommits: Votes{
CurrentWeight: uint32(len(precommits) + len(pcEquivocations)),
Missing: missingPrecommits,
},
},
Background: []RoundState{},
}

*res = roundstate
return nil
}

func thresholdWeight(totalWeight uint32) uint32 {
return totalWeight * 2 / 3
}

// difference get the values representing the difference, i.e., the values that are in voters but not in pre.
// this function returns the authorities that haven't voted yet
func difference(voters []ed25519.PublicKeyBytes, equivocations []ed25519.PublicKeyBytes) []ed25519.PublicKeyBytes {
diff := make([]ed25519.PublicKeyBytes, 0)
diffmap := make(map[ed25519.PublicKeyBytes]bool, len(voters))

for _, eq := range equivocations {
diffmap[eq] = true
}

for _, v := range voters {
if _, ok := diffmap[v]; !ok {
diff = append(diff, v)
}
}

return diff
}

func toAddress(pkb []ed25519.PublicKeyBytes) ([]string, error) {
addrs := make([]string, len(pkb))
for i, b := range pkb {
pk, err := ed25519.NewPublicKey(b[:])
if err != nil {
return nil, err
}

addrs[i] = string(pk.Address())
}

return addrs, nil
}
69 changes: 68 additions & 1 deletion dot/rpc/modules/grandpa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,17 @@ import (
"testing"

"github.com/ChainSafe/gossamer/dot/state"
"github.com/ChainSafe/gossamer/dot/types"
"github.com/ChainSafe/gossamer/lib/crypto/ed25519"
"github.com/ChainSafe/gossamer/lib/grandpa"
"github.com/ChainSafe/gossamer/lib/keystore"
"github.com/stretchr/testify/require"

rpcmocks "github.com/ChainSafe/gossamer/dot/rpc/modules/mocks"
)

var kr, _ = keystore.NewEd25519Keyring()

func TestGrandpaProveFinality(t *testing.T) {
testStateService := newTestStateService(t)

Expand All @@ -33,7 +42,7 @@ func TestGrandpaProveFinality(t *testing.T) {
t.Errorf("Fail: bestblock failed")
}

gmSvc := NewGrandpaModule(testStateService.Block)
gmSvc := NewGrandpaModule(testStateService.Block, nil)

testStateService.Block.SetJustification(bestBlock.Header.ParentHash, make([]byte, 10))
testStateService.Block.SetJustification(bestBlock.Header.Hash(), make([]byte, 11))
Expand All @@ -55,3 +64,61 @@ func TestGrandpaProveFinality(t *testing.T) {
t.Errorf("Fail: expected: %+v got: %+v\n", res, &expectedResponse)
}
}

func TestRoundSate(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

*RoundState

Copy link
Member Author

Choose a reason for hiding this comment

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

Done!

var voters grandpa.Voters

for _, k := range kr.Keys {
voters = append(voters, &types.GrandpaVoter{
Key: k.Public().(*ed25519.PublicKey),
ID: 1,
})
}

grandpamock := new(rpcmocks.MockBlockFinalityAPI)
grandpamock.On("GetVoters").Return(voters)
grandpamock.On("GetSetID").Return(uint64(0))
grandpamock.On("GetRound").Return(uint64(2))

grandpamock.On("PreVotes").Return([]ed25519.PublicKeyBytes{
kr.Alice().Public().(*ed25519.PublicKey).AsBytes(),
kr.Bob().Public().(*ed25519.PublicKey).AsBytes(),
kr.Charlie().Public().(*ed25519.PublicKey).AsBytes(),
kr.Dave().Public().(*ed25519.PublicKey).AsBytes(),
}, []ed25519.PublicKeyBytes{})

grandpamock.On("PreCommits").Return([]ed25519.PublicKeyBytes{
kr.Alice().Public().(*ed25519.PublicKey).AsBytes(),
kr.Bob().Public().(*ed25519.PublicKey).AsBytes(),
}, []ed25519.PublicKeyBytes{})

mod := NewGrandpaModule(nil, grandpamock)

res := new(RoundStateResponse)
err := mod.RoundState(nil, nil, res)

require.NoError(t, err)

// newTestVoters has actually 9 keys with weight of 1
require.Equal(t, uint32(9), res.Best.TotalWeight)
require.Equal(t, uint32(6), res.Best.ThresholdWeight)

expectedMissingPrevotes := []string{
string(kr.Eve().Public().Address()),
string(kr.Ferdie().Public().Address()),
string(kr.George().Public().Address()),
string(kr.Heather().Public().Address()),
string(kr.Ian().Public().Address()),
}

expectedMissingPrecommits := append([]string{
string(kr.Charlie().Public().Address()),
string(kr.Dave().Public().Address()),
}, expectedMissingPrevotes...)

require.Equal(t, expectedMissingPrevotes, res.Best.Prevotes.Missing)
require.Equal(t, expectedMissingPrecommits, res.Best.Precommits.Missing)

require.Equal(t, uint32(4), res.Best.Prevotes.CurrentWeight)
require.Equal(t, uint32(2), res.Best.Precommits.CurrentWeight)
}
109 changes: 109 additions & 0 deletions dot/rpc/modules/mocks/block_finality_api.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading