Skip to content

Commit 32cb636

Browse files
EclesioMeloJuniortimwu20
authored andcommitted
feat(lib/grandpa) add round state rpc call (ChainSafe#1664)
* 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
1 parent dc1f0c6 commit 32cb636

File tree

11 files changed

+386
-10
lines changed

11 files changed

+386
-10
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ endif
137137
@echo "> Generating mocks at $(path)"
138138

139139
ifeq ($(INMOCKS),1)
140-
cd $(path); $(GOPATH)/bin/mockery --name $(interface) --inpackage --keeptree --case underscore
140+
cd $(path); $(GOPATH)/bin/mockery --name $(interface) --structname Mock$(interface) --case underscore --keeptree
141141
else
142142
$(GOPATH)/bin/mockery --srcpkg $(path) --name $(interface) --case underscore --inpackage
143143
endif

dot/node.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ func NewNode(cfg *Config, ks *keystore.GlobalKeystore, stopFunc func()) (*Node,
309309

310310
// check if rpc service is enabled
311311
if enabled := cfg.RPC.Enabled || cfg.RPC.WS; enabled {
312-
rpcSrvc := createRPCService(cfg, stateSrvc, coreSrvc, networkSrvc, bp, rt, sysSrvc)
312+
rpcSrvc := createRPCService(cfg, stateSrvc, coreSrvc, networkSrvc, bp, rt, sysSrvc, fg)
313313
nodeSrvcs = append(nodeSrvcs, rpcSrvc)
314314
} else {
315315
logger.Debug("rpc service disabled by default", "rpc", enabled)

dot/rpc/http.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ type HTTPServerConfig struct {
4949
NetworkAPI modules.NetworkAPI
5050
CoreAPI modules.CoreAPI
5151
BlockProducerAPI modules.BlockProducerAPI
52+
BlockFinalityAPI modules.BlockFinalityAPI
5253
RuntimeAPI modules.RuntimeAPI
5354
TransactionQueueAPI modules.TransactionStateAPI
5455
RPCAPI modules.RPCAPI
@@ -100,7 +101,7 @@ func (h *HTTPServer) RegisterModules(mods []string) {
100101
case "chain":
101102
srvc = modules.NewChainModule(h.serverConfig.BlockAPI)
102103
case "grandpa":
103-
srvc = modules.NewGrandpaModule(h.serverConfig.BlockAPI)
104+
srvc = modules.NewGrandpaModule(h.serverConfig.BlockAPI, h.serverConfig.BlockFinalityAPI)
104105
case "state":
105106
srvc = modules.NewStateModule(h.serverConfig.NetworkAPI, h.serverConfig.StorageAPI, h.serverConfig.CoreAPI)
106107
case "rpc":

dot/rpc/modules/api.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"github.com/ChainSafe/gossamer/dot/types"
88
"github.com/ChainSafe/gossamer/lib/common"
99
"github.com/ChainSafe/gossamer/lib/crypto"
10+
"github.com/ChainSafe/gossamer/lib/crypto/ed25519"
11+
"github.com/ChainSafe/gossamer/lib/grandpa"
1012
"github.com/ChainSafe/gossamer/lib/runtime"
1113
"github.com/ChainSafe/gossamer/lib/transaction"
1214
)
@@ -93,3 +95,12 @@ type SystemAPI interface {
9395
ChainType() string
9496
ChainName() string
9597
}
98+
99+
// BlockFinalityAPI is the interface for handling block finalisation methods
100+
type BlockFinalityAPI interface {
101+
GetSetID() uint64
102+
GetRound() uint64
103+
GetVoters() grandpa.Voters
104+
PreVotes() []ed25519.PublicKeyBytes
105+
PreCommits() []ed25519.PublicKeyBytes
106+
}

dot/rpc/modules/grandpa.go

Lines changed: 109 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,45 @@ import (
2020
"net/http"
2121

2222
"github.com/ChainSafe/gossamer/lib/common"
23+
"github.com/ChainSafe/gossamer/lib/crypto/ed25519"
2324
)
2425

2526
// GrandpaModule init parameters
2627
type GrandpaModule struct {
27-
blockAPI BlockAPI
28+
blockAPI BlockAPI
29+
blockFinalityAPI BlockFinalityAPI
2830
}
2931

3032
// NewGrandpaModule creates a new Grandpa rpc module.
31-
func NewGrandpaModule(api BlockAPI) *GrandpaModule {
33+
func NewGrandpaModule(api BlockAPI, finalityAPI BlockFinalityAPI) *GrandpaModule {
3234
return &GrandpaModule{
33-
blockAPI: api,
35+
blockAPI: api,
36+
blockFinalityAPI: finalityAPI,
3437
}
3538
}
3639

40+
// Votes struct formats rpc call
41+
type Votes struct {
42+
CurrentWeight uint32 `json:"currentWeight"`
43+
Missing []string `json:"missing"`
44+
}
45+
46+
// RoundState json format for roundState RPC call
47+
type RoundState struct {
48+
Round uint32 `json:"round"`
49+
TotalWeight uint32 `json:"totalWeight"`
50+
ThresholdWeight uint32 `json:"thresholdWeight"`
51+
Prevotes Votes `json:"prevotes"`
52+
Precommits Votes `json:"precommits"`
53+
}
54+
55+
// RoundStateResponse response to roundState RPC call
56+
type RoundStateResponse struct {
57+
SetID uint32 `json:"setId"`
58+
Best RoundState `json:"best"`
59+
Background []RoundState `json:"background"`
60+
}
61+
3762
// ProveFinalityRequest request struct
3863
type ProveFinalityRequest struct {
3964
blockHashStart common.Hash
@@ -71,3 +96,84 @@ func (gm *GrandpaModule) ProveFinality(r *http.Request, req *ProveFinalityReques
7196

7297
return nil
7398
}
99+
100+
// RoundState returns the state of the current best round state as well as the ongoing background rounds.
101+
func (gm *GrandpaModule) RoundState(r *http.Request, req *EmptyRequest, res *RoundStateResponse) error {
102+
voters := gm.blockFinalityAPI.GetVoters()
103+
votersPkBytes := make([]ed25519.PublicKeyBytes, len(voters))
104+
for i, v := range voters {
105+
votersPkBytes[i] = v.PublicKeyBytes()
106+
}
107+
108+
votes := gm.blockFinalityAPI.PreVotes()
109+
commits := gm.blockFinalityAPI.PreCommits()
110+
111+
missingPrevotes, err := toAddress(difference(votersPkBytes, votes))
112+
if err != nil {
113+
return err
114+
}
115+
116+
missingPrecommits, err := toAddress(difference(votersPkBytes, commits))
117+
if err != nil {
118+
return err
119+
}
120+
121+
totalWeight := uint32(len(voters))
122+
roundstate := RoundStateResponse{
123+
SetID: uint32(gm.blockFinalityAPI.GetSetID()),
124+
Best: RoundState{
125+
Round: uint32(gm.blockFinalityAPI.GetRound()),
126+
ThresholdWeight: thresholdWeight(totalWeight),
127+
TotalWeight: totalWeight,
128+
Prevotes: Votes{
129+
CurrentWeight: uint32(len(votes)),
130+
Missing: missingPrevotes,
131+
},
132+
Precommits: Votes{
133+
CurrentWeight: uint32(len(commits)),
134+
Missing: missingPrecommits,
135+
},
136+
},
137+
Background: []RoundState{},
138+
}
139+
140+
*res = roundstate
141+
return nil
142+
}
143+
144+
func thresholdWeight(totalWeight uint32) uint32 {
145+
return totalWeight * 2 / 3
146+
}
147+
148+
// difference get the values representing the difference, i.e., the values that are in voters but not in pre.
149+
// this function returns the authorities that haven't voted yet
150+
func difference(voters, equivocations []ed25519.PublicKeyBytes) []ed25519.PublicKeyBytes {
151+
diff := make([]ed25519.PublicKeyBytes, 0)
152+
diffmap := make(map[ed25519.PublicKeyBytes]bool, len(voters))
153+
154+
for _, eq := range equivocations {
155+
diffmap[eq] = true
156+
}
157+
158+
for _, v := range voters {
159+
if _, ok := diffmap[v]; !ok {
160+
diff = append(diff, v)
161+
}
162+
}
163+
164+
return diff
165+
}
166+
167+
func toAddress(pkb []ed25519.PublicKeyBytes) ([]string, error) {
168+
addrs := make([]string, len(pkb))
169+
for i, b := range pkb {
170+
pk, err := ed25519.NewPublicKey(b[:])
171+
if err != nil {
172+
return nil, err
173+
}
174+
175+
addrs[i] = string(pk.Address())
176+
}
177+
178+
return addrs, nil
179+
}

dot/rpc/modules/grandpa_test.go

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,17 @@ import (
2121
"testing"
2222

2323
"github.com/ChainSafe/gossamer/dot/state"
24+
"github.com/ChainSafe/gossamer/dot/types"
25+
"github.com/ChainSafe/gossamer/lib/crypto/ed25519"
26+
"github.com/ChainSafe/gossamer/lib/grandpa"
27+
"github.com/ChainSafe/gossamer/lib/keystore"
28+
"github.com/stretchr/testify/require"
29+
30+
rpcmocks "github.com/ChainSafe/gossamer/dot/rpc/modules/mocks"
2431
)
2532

33+
var kr, _ = keystore.NewEd25519Keyring()
34+
2635
func TestGrandpaProveFinality(t *testing.T) {
2736
testStateService := newTestStateService(t)
2837

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

36-
gmSvc := NewGrandpaModule(testStateService.Block)
45+
gmSvc := NewGrandpaModule(testStateService.Block, nil)
3746

3847
testStateService.Block.SetJustification(bestBlock.Header.ParentHash, make([]byte, 10))
3948
testStateService.Block.SetJustification(bestBlock.Header.Hash(), make([]byte, 11))
@@ -55,3 +64,61 @@ func TestGrandpaProveFinality(t *testing.T) {
5564
t.Errorf("Fail: expected: %+v got: %+v\n", res, &expectedResponse)
5665
}
5766
}
67+
68+
func TestRoundState(t *testing.T) {
69+
var voters grandpa.Voters
70+
71+
for _, k := range kr.Keys {
72+
voters = append(voters, &types.GrandpaVoter{
73+
Key: k.Public().(*ed25519.PublicKey),
74+
ID: 1,
75+
})
76+
}
77+
78+
grandpamock := new(rpcmocks.MockBlockFinalityAPI)
79+
grandpamock.On("GetVoters").Return(voters)
80+
grandpamock.On("GetSetID").Return(uint64(0))
81+
grandpamock.On("GetRound").Return(uint64(2))
82+
83+
grandpamock.On("PreVotes").Return([]ed25519.PublicKeyBytes{
84+
kr.Alice().Public().(*ed25519.PublicKey).AsBytes(),
85+
kr.Bob().Public().(*ed25519.PublicKey).AsBytes(),
86+
kr.Charlie().Public().(*ed25519.PublicKey).AsBytes(),
87+
kr.Dave().Public().(*ed25519.PublicKey).AsBytes(),
88+
})
89+
90+
grandpamock.On("PreCommits").Return([]ed25519.PublicKeyBytes{
91+
kr.Alice().Public().(*ed25519.PublicKey).AsBytes(),
92+
kr.Bob().Public().(*ed25519.PublicKey).AsBytes(),
93+
})
94+
95+
mod := NewGrandpaModule(nil, grandpamock)
96+
97+
res := new(RoundStateResponse)
98+
err := mod.RoundState(nil, nil, res)
99+
100+
require.NoError(t, err)
101+
102+
// newTestVoters has actually 9 keys with weight of 1
103+
require.Equal(t, uint32(9), res.Best.TotalWeight)
104+
require.Equal(t, uint32(6), res.Best.ThresholdWeight)
105+
106+
expectedMissingPrevotes := []string{
107+
string(kr.Eve().Public().Address()),
108+
string(kr.Ferdie().Public().Address()),
109+
string(kr.George().Public().Address()),
110+
string(kr.Heather().Public().Address()),
111+
string(kr.Ian().Public().Address()),
112+
}
113+
114+
expectedMissingPrecommits := append([]string{
115+
string(kr.Charlie().Public().Address()),
116+
string(kr.Dave().Public().Address()),
117+
}, expectedMissingPrevotes...)
118+
119+
require.Equal(t, expectedMissingPrevotes, res.Best.Prevotes.Missing)
120+
require.Equal(t, expectedMissingPrecommits, res.Best.Precommits.Missing)
121+
122+
require.Equal(t, uint32(4), res.Best.Prevotes.CurrentWeight)
123+
require.Equal(t, uint32(2), res.Best.Precommits.CurrentWeight)
124+
}

dot/rpc/modules/mocks/block_finality_api.go

Lines changed: 91 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dot/services.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ func createNetworkService(cfg *Config, stateSrvc *state.Service) (*network.Servi
317317
// RPC Service
318318

319319
// createRPCService creates the RPC service from the provided core configuration
320-
func createRPCService(cfg *Config, stateSrvc *state.Service, coreSrvc *core.Service, networkSrvc *network.Service, bp modules.BlockProducerAPI, rt runtime.Instance, sysSrvc *system.Service) *rpc.HTTPServer {
320+
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 {
321321
logger.Info(
322322
"creating rpc service...",
323323
"host", cfg.RPC.Host,
@@ -337,6 +337,7 @@ func createRPCService(cfg *Config, stateSrvc *state.Service, coreSrvc *core.Serv
337337
NetworkAPI: networkSrvc,
338338
CoreAPI: coreSrvc,
339339
BlockProducerAPI: bp,
340+
BlockFinalityAPI: finSrvc,
340341
RuntimeAPI: rt,
341342
TransactionQueueAPI: stateSrvc.Transaction,
342343
RPCAPI: rpcService,

0 commit comments

Comments
 (0)