Skip to content

Commit 86df957

Browse files
EclesioMeloJuniorarijitADnoot
authored
feat(dot/rpc) implement author_hasSessionKeys RPC call (#1704)
* 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: add grandpa subscribe justification rpc call * chore: add cancel function to stop goroutine when unsuubscribe * chore: resolve lint * chore: add tests to subscribe justification call * remove deps from round state rpc call pr * chore: remove unecessary changes * remove inpackage well mock is in mocks folder * chore: fix lint * wip: fixing tests * chore: use channels to control goroutines * chore: resolve lint * add time.Duration on structs * chore: add runtime method and rpc method * wip: fix runtime response scale decoding * chore: adding data to tests * wip: improve test coverage and assertions * chore: hasSessionKey rpc call done * chore: change config.toml back * chore: remove log, add string.EqualFold * chore: remove unused logs, get back new private key * update hasSessionKey method to use coreapi insted of runtime api * chore: resolve lint issues * chore: change from []byte to []uint8 * chore: fix tests failures * chore: fix HasSessionKeyResponse comments Co-authored-by: noot <[email protected]> * chore: fix KeyTypeID comments Co-authored-by: noot <[email protected]> * chore: update comments, unexport struct and remove qtyCheck * chore: improve hasSessionKey key check * chore: update params to represent better data * chore: improve func name to DecodeKeyPairFromHex * chore: get back func names to avoid naming conflicts * chore: update go.sum * chore: update grandpa_subscribeJustification return response * chore: fix lint issues * chore: check the len of the slice of decoded keys * chore: update exports comments * chore: update the import style * chore: improve DecodeKeyPairFromHex export comment * chore: hasSessionKeys test fixed * chore: improve export function comment * chore: group rpc methods string in a unique place * chore: use chan struct{} * chore: fix tests that uses wsconn * chore: fix subscription test * chore: improve log message * chore: fix lint issues * chore: increase the author RPC method qty * chore: fix deepsource style error Co-authored-by: Arijit Das <[email protected]> Co-authored-by: noot <[email protected]>
1 parent e7226ae commit 86df957

File tree

21 files changed

+748
-171
lines changed

21 files changed

+748
-171
lines changed

dot/core/service.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,16 @@ func (s *Service) HasKey(pubKeyStr, keyType string) (bool, error) {
461461
return keystore.HasKey(pubKeyStr, keyType, s.keys.Acco)
462462
}
463463

464+
// DecodeSessionKeys executes the runtime DecodeSessionKeys and return the scale encoded keys
465+
func (s *Service) DecodeSessionKeys(enc []byte) ([]byte, error) {
466+
rt, err := s.blockState.GetRuntime(nil)
467+
if err != nil {
468+
return nil, err
469+
}
470+
471+
return rt.DecodeSessionKeys(enc)
472+
}
473+
464474
// GetRuntimeVersion gets the current RuntimeVersion
465475
func (s *Service) GetRuntimeVersion(bhash *common.Hash) (runtime.Version, error) {
466476
var stateRootHash *common.Hash

dot/rpc/http.go

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -233,15 +233,13 @@ func (h *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
233233
// NewWSConn to create new WebSocket Connection struct
234234
func NewWSConn(conn *websocket.Conn, cfg *HTTPServerConfig) *subscription.WSConn {
235235
c := &subscription.WSConn{
236-
Wsconn: conn,
237-
Subscriptions: make(map[uint]subscription.Listener),
238-
BlockSubChannels: make(map[uint]byte),
239-
StorageSubChannels: make(map[int]byte),
240-
StorageAPI: cfg.StorageAPI,
241-
BlockAPI: cfg.BlockAPI,
242-
CoreAPI: cfg.CoreAPI,
243-
TxStateAPI: cfg.TransactionQueueAPI,
244-
RPCHost: fmt.Sprintf("http://%s:%d/", cfg.Host, cfg.RPCPort),
236+
Wsconn: conn,
237+
Subscriptions: make(map[uint32]subscription.Listener),
238+
StorageAPI: cfg.StorageAPI,
239+
BlockAPI: cfg.BlockAPI,
240+
CoreAPI: cfg.CoreAPI,
241+
TxStateAPI: cfg.TransactionQueueAPI,
242+
RPCHost: fmt.Sprintf("http://%s:%d/", cfg.Host, cfg.RPCPort),
245243
HTTP: &http.Client{
246244
Timeout: time.Second * 30,
247245
},

dot/rpc/modules/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ type CoreAPI interface {
7676
GetRuntimeVersion(bhash *common.Hash) (runtime.Version, error)
7777
HandleSubmittedExtrinsic(types.Extrinsic) error
7878
GetMetadata(bhash *common.Hash) ([]byte, error)
79+
DecodeSessionKeys(enc []byte) ([]byte, error)
7980
}
8081

8182
// RPCAPI is the interface for methods related to RPC service

dot/rpc/modules/author.go

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@
1717
package modules
1818

1919
import (
20-
"fmt"
20+
"errors"
2121
"net/http"
22-
"reflect"
22+
"strings"
2323

2424
"github.com/ChainSafe/gossamer/dot/types"
2525
"github.com/ChainSafe/gossamer/lib/common"
2626
"github.com/ChainSafe/gossamer/lib/keystore"
27+
"github.com/ChainSafe/gossamer/pkg/scale"
2728

2829
log "github.com/ChainSafe/log15"
2930
)
@@ -35,8 +36,17 @@ type AuthorModule struct {
3536
txStateAPI TransactionStateAPI
3637
}
3738

39+
// HasSessionKeyRequest is used to receive the rpc data
40+
type HasSessionKeyRequest struct {
41+
PublicKeys string
42+
}
43+
3844
// KeyInsertRequest is used as model for the JSON
39-
type KeyInsertRequest []string
45+
type KeyInsertRequest struct {
46+
Type string
47+
Seed string
48+
PublicKey string
49+
}
4050

4151
// Extrinsic represents a hex-encoded extrinsic
4252
type Extrinsic struct {
@@ -64,6 +74,18 @@ type RemoveExtrinsicsResponse []common.Hash
6474
// KeyRotateResponse is a byte array used to rotate
6575
type KeyRotateResponse []byte
6676

77+
// HasSessionKeyResponse is the response to the RPC call author_hasSessionKeys
78+
type HasSessionKeyResponse bool
79+
80+
// KeyTypeID represents the key type of a session key
81+
type keyTypeID [4]uint8
82+
83+
// DecodedKey is the representation of a scaled decoded public key
84+
type decodedKey struct {
85+
Data []uint8
86+
Type keyTypeID
87+
}
88+
6789
// ExtrinsicStatus holds the actual valid statuses
6890
type ExtrinsicStatus struct {
6991
IsFuture bool
@@ -94,27 +116,66 @@ func NewAuthorModule(logger log.Logger, coreAPI CoreAPI, txStateAPI TransactionS
94116
}
95117
}
96118

97-
// InsertKey inserts a key into the keystore
98-
func (am *AuthorModule) InsertKey(r *http.Request, req *KeyInsertRequest, res *KeyInsertResponse) error {
99-
keyReq := *req
119+
// HasSessionKeys checks if the keystore has private keys for the given session public keys.
120+
func (am *AuthorModule) HasSessionKeys(r *http.Request, req *HasSessionKeyRequest, res *HasSessionKeyResponse) error {
121+
pubKeysBytes, err := common.HexToBytes(req.PublicKeys)
122+
if err != nil {
123+
return err
124+
}
125+
126+
pkeys, err := scale.Marshal(pubKeysBytes)
127+
if err != nil {
128+
return err
129+
}
100130

101-
pkDec, err := common.HexToBytes(keyReq[1])
131+
data, err := am.coreAPI.DecodeSessionKeys(pkeys)
102132
if err != nil {
133+
*res = false
103134
return err
104135
}
105136

106-
privateKey, err := keystore.DecodePrivateKey(pkDec, keystore.DetermineKeyType(keyReq[0]))
137+
var decodedKeys *[]decodedKey
138+
err = scale.Unmarshal(data, &decodedKeys)
139+
if err != nil {
140+
return err
141+
}
142+
143+
if decodedKeys == nil || len(*decodedKeys) < 1 {
144+
*res = false
145+
return nil
146+
}
147+
148+
for _, key := range *decodedKeys {
149+
encType := keystore.Name(key.Type[:])
150+
ok, err := am.coreAPI.HasKey(common.BytesToHex(key.Data), string(encType))
151+
152+
if err != nil || !ok {
153+
*res = false
154+
return err
155+
}
156+
}
157+
158+
*res = true
159+
return nil
160+
}
161+
162+
// InsertKey inserts a key into the keystore
163+
func (am *AuthorModule) InsertKey(r *http.Request, req *KeyInsertRequest, res *KeyInsertResponse) error {
164+
keyReq := *req
165+
166+
keyBytes, err := common.HexToBytes(req.Seed)
107167
if err != nil {
108168
return err
109169
}
110170

111-
keyPair, err := keystore.PrivateKeyToKeypair(privateKey)
171+
keyPair, err := keystore.DecodeKeyPairFromHex(keyBytes, keystore.DetermineKeyType(keyReq.Type))
112172
if err != nil {
113173
return err
114174
}
115175

116-
if !reflect.DeepEqual(keyPair.Public().Hex(), keyReq[2]) {
117-
return fmt.Errorf("generated public key does not equal provide public key")
176+
//strings.EqualFold compare using case-insensitivity.
177+
if !strings.EqualFold(keyPair.Public().Hex(), keyReq.PublicKey) {
178+
return errors.New("generated public key does not equal provide public key")
118179
}
119180

120181
am.coreAPI.InsertKey(keyPair)

dot/rpc/modules/author_test.go

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,78 @@ import (
88
apimocks "github.com/ChainSafe/gossamer/dot/rpc/modules/mocks"
99
"github.com/ChainSafe/gossamer/dot/types"
1010
"github.com/ChainSafe/gossamer/lib/common"
11+
"github.com/ChainSafe/gossamer/lib/crypto/sr25519"
1112
"github.com/ChainSafe/gossamer/lib/keystore"
13+
"github.com/ChainSafe/gossamer/lib/runtime"
14+
"github.com/ChainSafe/gossamer/lib/runtime/wasmer"
1215
"github.com/ChainSafe/gossamer/lib/transaction"
1316
log "github.com/ChainSafe/log15"
1417
"github.com/google/go-cmp/cmp"
1518
"github.com/stretchr/testify/mock"
19+
"github.com/stretchr/testify/require"
1620
)
1721

22+
func TestAuthorModule_HasSessionKey(t *testing.T) {
23+
globalStore := keystore.NewGlobalKeystore()
24+
25+
coremockapi := new(apimocks.MockCoreAPI)
26+
mockInsertKey := coremockapi.On("InsertKey", mock.AnythingOfType("*sr25519.Keypair"))
27+
mockInsertKey.Run(func(args mock.Arguments) {
28+
kp := args.Get(0).(*sr25519.Keypair)
29+
globalStore.Acco.Insert(kp)
30+
})
31+
32+
mockHasKey := coremockapi.On("HasKey", mock.AnythingOfType("string"), mock.AnythingOfType("string"))
33+
mockHasKey.Run(func(args mock.Arguments) {
34+
pubKeyHex := args.Get(0).(string)
35+
keyType := args.Get(1).(string)
36+
37+
ok, err := keystore.HasKey(pubKeyHex, keyType, globalStore.Acco)
38+
mockHasKey.ReturnArguments = []interface{}{ok, err}
39+
})
40+
41+
keys := "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d34309a9d2a24213896ff06895db16aade8b6502f3a71cf56374cc3852042602634309a9d2a24213896ff06895db16aade8b6502f3a71cf56374cc3852042602634309a9d2a24213896ff06895db16aade8b6502f3a71cf56374cc38520426026"
42+
runtimeInstance := wasmer.NewTestInstance(t, runtime.NODE_RUNTIME)
43+
44+
decodeSessionKeysMock := coremockapi.On("DecodeSessionKeys", mock.AnythingOfType("[]uint8"))
45+
decodeSessionKeysMock.Run(func(args mock.Arguments) {
46+
b := args.Get(0).([]byte)
47+
dec, err := runtimeInstance.DecodeSessionKeys(b)
48+
decodeSessionKeysMock.ReturnArguments = []interface{}{dec, err}
49+
})
50+
51+
module := &AuthorModule{
52+
coreAPI: coremockapi,
53+
logger: log.New("service", "RPC", "module", "author"),
54+
}
55+
56+
req := &HasSessionKeyRequest{
57+
PublicKeys: keys,
58+
}
59+
60+
err := module.InsertKey(nil, &KeyInsertRequest{
61+
Type: "babe",
62+
Seed: "0xfec0f475b818470af5caf1f3c1b1558729961161946d581d2755f9fb566534f8",
63+
PublicKey: "0x34309a9d2a24213896ff06895db16aade8b6502f3a71cf56374cc38520426026",
64+
}, nil)
65+
coremockapi.AssertCalled(t, "InsertKey", mock.AnythingOfType("*sr25519.Keypair"))
66+
require.NoError(t, err)
67+
require.Equal(t, 1, globalStore.Acco.Size())
68+
69+
err = module.InsertKey(nil, &KeyInsertRequest{
70+
Type: "babe",
71+
Seed: "0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a",
72+
PublicKey: "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d",
73+
}, nil)
74+
require.NoError(t, err)
75+
require.Equal(t, 2, globalStore.Acco.Size())
76+
77+
var res HasSessionKeyResponse
78+
err = module.HasSessionKeys(nil, req, &res)
79+
require.NoError(t, err)
80+
require.True(t, bool(res))
81+
}
82+
1883
func TestAuthorModule_SubmitExtrinsic(t *testing.T) {
1984
errMockCoreAPI := &apimocks.MockCoreAPI{}
2085
errMockCoreAPI.On("HandleSubmittedExtrinsic", mock.AnythingOfType("types.Extrinsic")).Return(fmt.Errorf("some error"))
@@ -202,8 +267,8 @@ func TestAuthorModule_InsertKey(t *testing.T) {
202267
args: args{
203268
req: &KeyInsertRequest{
204269
"babe",
205-
"0xb7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309",
206270
"0x6246ddf254e0b4b4e7dffefc8adf69d212b98ac2b579c362b473fec8c40b4c0a",
271+
"0xdad5131003242c37c227f744f82118dd59a24b949ae264a93d949100738c196c",
207272
},
208273
},
209274
},
@@ -214,9 +279,10 @@ func TestAuthorModule_InsertKey(t *testing.T) {
214279
coreAPI: mockCoreAPI,
215280
},
216281
args: args{
217-
req: &KeyInsertRequest{"gran",
218-
"0xb7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309b7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309",
219-
"0xb7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309",
282+
req: &KeyInsertRequest{
283+
"gran",
284+
"0xb48004c6e1625282313b07d1c9950935e86894a2e4f21fb1ffee9854d180c781",
285+
"0xa7d6507d59f8871b8f1a0f2c32e219adfacff4c9fcb05b0b2d8ebd6a65c88ee6",
220286
},
221287
},
222288
},

dot/rpc/modules/mocks/core_api.go

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

dot/rpc/service_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func TestNewService(t *testing.T) {
3535
func TestService_Methods(t *testing.T) {
3636
qtySystemMethods := 13
3737
qtyRPCMethods := 1
38-
qtyAuthorMethods := 7
38+
qtyAuthorMethods := 8
3939

4040
rpcService := NewService()
4141
sysMod := modules.NewSystemModule(nil, nil, nil, nil, nil, nil)

0 commit comments

Comments
 (0)