Skip to content

Commit

Permalink
feat(lib/grandpa) add round state rpc call (ChainSafe#1664)
Browse files Browse the repository at this point in the history
* chore: add interface for grandpa in rpc pkg

* chore: create roundState rpc call

* chore: coment unused branch

* chore: fix lint

* chore: add test case

* chore: fix lint

* chore: address comments

* chore: fix the diff implementation

* chore: address comment

* chore: change PreVotes and PreCommits interface signature

* chore: remove check

* chore: improve code defs
  • Loading branch information
EclesioMeloJunior authored and timwu20 committed Dec 6, 2021
1 parent dc1f0c6 commit 32cb636
Show file tree
Hide file tree
Showing 11 changed files with 386 additions and 10 deletions.
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 @@ -49,6 +49,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 @@ -100,7 +101,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
PreCommits() []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()
}

votes := gm.blockFinalityAPI.PreVotes()
commits := gm.blockFinalityAPI.PreCommits()

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

missingPrecommits, err := toAddress(difference(votersPkBytes, commits))
if err != nil {
return err
}

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(votes)),
Missing: missingPrevotes,
},
Precommits: Votes{
CurrentWeight: uint32(len(commits)),
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, 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 TestRoundState(t *testing.T) {
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(),
})

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

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)
}
91 changes: 91 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.

3 changes: 2 additions & 1 deletion dot/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ func createNetworkService(cfg *Config, stateSrvc *state.Service) (*network.Servi
// RPC Service

// createRPCService creates the RPC service from the provided core configuration
func createRPCService(cfg *Config, stateSrvc *state.Service, coreSrvc *core.Service, networkSrvc *network.Service, bp modules.BlockProducerAPI, rt runtime.Instance, sysSrvc *system.Service) *rpc.HTTPServer {
func createRPCService(cfg *Config, stateSrvc *state.Service, coreSrvc *core.Service, networkSrvc *network.Service, bp modules.BlockProducerAPI, rt runtime.Instance, sysSrvc *system.Service, finSrvc *grandpa.Service) *rpc.HTTPServer {
logger.Info(
"creating rpc service...",
"host", cfg.RPC.Host,
Expand All @@ -337,6 +337,7 @@ func createRPCService(cfg *Config, stateSrvc *state.Service, coreSrvc *core.Serv
NetworkAPI: networkSrvc,
CoreAPI: coreSrvc,
BlockProducerAPI: bp,
BlockFinalityAPI: finSrvc,
RuntimeAPI: rt,
TransactionQueueAPI: stateSrvc.Transaction,
RPCAPI: rpcService,
Expand Down
Loading

0 comments on commit 32cb636

Please sign in to comment.