From c47a217c3fefb5a28511fb160c62e26a96b9e6e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ecl=C3=A9sio=20Junior?= Date: Fri, 6 Aug 2021 13:53:18 -0400 Subject: [PATCH] feat(dot/rpc) implement `author_hasSessionKeys` RPC call (#1715) * 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 <36753753+noot@users.noreply.github.com> * chore: fix KeyTypeID comments Co-authored-by: noot <36753753+noot@users.noreply.github.com> * 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 * chore: keep just related changes * chore: improve test coverage * chore: improve test coverage on lib/keystore/helpers.go * chore: improve testing and remove unused function * chore: create more test cases to hasSessionKey RPC method Co-authored-by: Arijit Das Co-authored-by: noot <36753753+noot@users.noreply.github.com> --- dot/core/mocks/block_producer.go | 2 +- dot/core/mocks/block_state.go | 443 +++++++++++++++++++++++++++++ dot/core/mocks/digest_handler.go | 2 +- dot/core/mocks/network.go | 2 +- dot/core/service.go | 10 + dot/core/service_test.go | 37 +++ dot/rpc/modules/api.go | 1 + dot/rpc/modules/author.go | 83 +++++- dot/rpc/modules/author_test.go | 157 +++++++++- dot/rpc/modules/mocks/core_api.go | 37 ++- dot/rpc/service_test.go | 2 +- go.sum | 1 - lib/crypto/sr25519/sr25519.go | 8 +- lib/crypto/sr25519/sr25519_test.go | 7 + lib/keystore/helpers.go | 14 + lib/keystore/helpers_test.go | 32 +++ lib/runtime/constants.go | 2 + lib/runtime/interface.go | 1 + lib/runtime/life/exports.go | 5 + lib/runtime/mocks/instance.go | 431 ++++++++++++++++++++++++++++ lib/runtime/wasmer/exports.go | 5 + lib/runtime/wasmer/exports_test.go | 23 ++ lib/runtime/wasmtime/exports.go | 5 + 23 files changed, 1274 insertions(+), 36 deletions(-) create mode 100644 dot/core/mocks/block_state.go create mode 100644 lib/runtime/mocks/instance.go diff --git a/dot/core/mocks/block_producer.go b/dot/core/mocks/block_producer.go index 341cc96b25..16c549ab1a 100644 --- a/dot/core/mocks/block_producer.go +++ b/dot/core/mocks/block_producer.go @@ -1,6 +1,6 @@ // Code generated by mockery v2.8.0. DO NOT EDIT. -package core +package mocks import ( types "github.com/ChainSafe/gossamer/dot/types" diff --git a/dot/core/mocks/block_state.go b/dot/core/mocks/block_state.go new file mode 100644 index 0000000000..7686c23dab --- /dev/null +++ b/dot/core/mocks/block_state.go @@ -0,0 +1,443 @@ +// Code generated by mockery v2.8.0. DO NOT EDIT. + +package mocks + +import ( + big "math/big" + + common "github.com/ChainSafe/gossamer/lib/common" + + mock "github.com/stretchr/testify/mock" + + runtime "github.com/ChainSafe/gossamer/lib/runtime" + + storage "github.com/ChainSafe/gossamer/lib/runtime/storage" + + types "github.com/ChainSafe/gossamer/dot/types" +) + +// MockBlockState is an autogenerated mock type for the BlockState type +type MockBlockState struct { + mock.Mock +} + +// AddBlock provides a mock function with given fields: _a0 +func (_m *MockBlockState) AddBlock(_a0 *types.Block) error { + ret := _m.Called(_a0) + + var r0 error + if rf, ok := ret.Get(0).(func(*types.Block) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// BestBlock provides a mock function with given fields: +func (_m *MockBlockState) BestBlock() (*types.Block, error) { + ret := _m.Called() + + var r0 *types.Block + if rf, ok := ret.Get(0).(func() *types.Block); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Block) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BestBlockHash provides a mock function with given fields: +func (_m *MockBlockState) BestBlockHash() common.Hash { + ret := _m.Called() + + var r0 common.Hash + if rf, ok := ret.Get(0).(func() common.Hash); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(common.Hash) + } + } + + return r0 +} + +// BestBlockHeader provides a mock function with given fields: +func (_m *MockBlockState) BestBlockHeader() (*types.Header, error) { + ret := _m.Called() + + var r0 *types.Header + if rf, ok := ret.Get(0).(func() *types.Header); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Header) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BestBlockNumber provides a mock function with given fields: +func (_m *MockBlockState) BestBlockNumber() (*big.Int, error) { + ret := _m.Called() + + var r0 *big.Int + if rf, ok := ret.Get(0).(func() *big.Int); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BestBlockStateRoot provides a mock function with given fields: +func (_m *MockBlockState) BestBlockStateRoot() (common.Hash, error) { + ret := _m.Called() + + var r0 common.Hash + if rf, ok := ret.Get(0).(func() common.Hash); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(common.Hash) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GenesisHash provides a mock function with given fields: +func (_m *MockBlockState) GenesisHash() common.Hash { + ret := _m.Called() + + var r0 common.Hash + if rf, ok := ret.Get(0).(func() common.Hash); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(common.Hash) + } + } + + return r0 +} + +// GetAllBlocksAtDepth provides a mock function with given fields: hash +func (_m *MockBlockState) GetAllBlocksAtDepth(hash common.Hash) []common.Hash { + ret := _m.Called(hash) + + var r0 []common.Hash + if rf, ok := ret.Get(0).(func(common.Hash) []common.Hash); ok { + r0 = rf(hash) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]common.Hash) + } + } + + return r0 +} + +// GetBlockBody provides a mock function with given fields: hash +func (_m *MockBlockState) GetBlockBody(hash common.Hash) (*types.Body, error) { + ret := _m.Called(hash) + + var r0 *types.Body + if rf, ok := ret.Get(0).(func(common.Hash) *types.Body); ok { + r0 = rf(hash) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Body) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(common.Hash) error); ok { + r1 = rf(hash) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetBlockByHash provides a mock function with given fields: _a0 +func (_m *MockBlockState) GetBlockByHash(_a0 common.Hash) (*types.Block, error) { + ret := _m.Called(_a0) + + var r0 *types.Block + if rf, ok := ret.Get(0).(func(common.Hash) *types.Block); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Block) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(common.Hash) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetFinalisedHash provides a mock function with given fields: _a0, _a1 +func (_m *MockBlockState) GetFinalisedHash(_a0 uint64, _a1 uint64) (common.Hash, error) { + ret := _m.Called(_a0, _a1) + + var r0 common.Hash + if rf, ok := ret.Get(0).(func(uint64, uint64) common.Hash); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(common.Hash) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(uint64, uint64) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetFinalisedHeader provides a mock function with given fields: _a0, _a1 +func (_m *MockBlockState) GetFinalisedHeader(_a0 uint64, _a1 uint64) (*types.Header, error) { + ret := _m.Called(_a0, _a1) + + var r0 *types.Header + if rf, ok := ret.Get(0).(func(uint64, uint64) *types.Header); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Header) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(uint64, uint64) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetRuntime provides a mock function with given fields: _a0 +func (_m *MockBlockState) GetRuntime(_a0 *common.Hash) (runtime.Instance, error) { + ret := _m.Called(_a0) + + var r0 runtime.Instance + if rf, ok := ret.Get(0).(func(*common.Hash) runtime.Instance); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(runtime.Instance) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(*common.Hash) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetSlotForBlock provides a mock function with given fields: _a0 +func (_m *MockBlockState) GetSlotForBlock(_a0 common.Hash) (uint64, error) { + ret := _m.Called(_a0) + + var r0 uint64 + if rf, ok := ret.Get(0).(func(common.Hash) uint64); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(uint64) + } + + var r1 error + if rf, ok := ret.Get(1).(func(common.Hash) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// HandleRuntimeChanges provides a mock function with given fields: newState, in, bHash +func (_m *MockBlockState) HandleRuntimeChanges(newState *storage.TrieState, in runtime.Instance, bHash common.Hash) error { + ret := _m.Called(newState, in, bHash) + + var r0 error + if rf, ok := ret.Get(0).(func(*storage.TrieState, runtime.Instance, common.Hash) error); ok { + r0 = rf(newState, in, bHash) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// HighestCommonAncestor provides a mock function with given fields: a, b +func (_m *MockBlockState) HighestCommonAncestor(a common.Hash, b common.Hash) (common.Hash, error) { + ret := _m.Called(a, b) + + var r0 common.Hash + if rf, ok := ret.Get(0).(func(common.Hash, common.Hash) common.Hash); ok { + r0 = rf(a, b) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(common.Hash) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(common.Hash, common.Hash) error); ok { + r1 = rf(a, b) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RegisterFinalizedChannel provides a mock function with given fields: ch +func (_m *MockBlockState) RegisterFinalizedChannel(ch chan<- *types.FinalisationInfo) (byte, error) { + ret := _m.Called(ch) + + var r0 byte + if rf, ok := ret.Get(0).(func(chan<- *types.FinalisationInfo) byte); ok { + r0 = rf(ch) + } else { + r0 = ret.Get(0).(byte) + } + + var r1 error + if rf, ok := ret.Get(1).(func(chan<- *types.FinalisationInfo) error); ok { + r1 = rf(ch) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RegisterImportedChannel provides a mock function with given fields: ch +func (_m *MockBlockState) RegisterImportedChannel(ch chan<- *types.Block) (byte, error) { + ret := _m.Called(ch) + + var r0 byte + if rf, ok := ret.Get(0).(func(chan<- *types.Block) byte); ok { + r0 = rf(ch) + } else { + r0 = ret.Get(0).(byte) + } + + var r1 error + if rf, ok := ret.Get(1).(func(chan<- *types.Block) error); ok { + r1 = rf(ch) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SetFinalisedHash provides a mock function with given fields: _a0, _a1, _a2 +func (_m *MockBlockState) SetFinalisedHash(_a0 common.Hash, _a1 uint64, _a2 uint64) error { + ret := _m.Called(_a0, _a1, _a2) + + var r0 error + if rf, ok := ret.Get(0).(func(common.Hash, uint64, uint64) error); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// StoreRuntime provides a mock function with given fields: _a0, _a1 +func (_m *MockBlockState) StoreRuntime(_a0 common.Hash, _a1 runtime.Instance) { + _m.Called(_a0, _a1) +} + +// SubChain provides a mock function with given fields: start, end +func (_m *MockBlockState) SubChain(start common.Hash, end common.Hash) ([]common.Hash, error) { + ret := _m.Called(start, end) + + var r0 []common.Hash + if rf, ok := ret.Get(0).(func(common.Hash, common.Hash) []common.Hash); ok { + r0 = rf(start, end) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]common.Hash) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(common.Hash, common.Hash) error); ok { + r1 = rf(start, end) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UnregisterFinalisedChannel provides a mock function with given fields: id +func (_m *MockBlockState) UnregisterFinalisedChannel(id byte) { + _m.Called(id) +} + +// UnregisterImportedChannel provides a mock function with given fields: id +func (_m *MockBlockState) UnregisterImportedChannel(id byte) { + _m.Called(id) +} diff --git a/dot/core/mocks/digest_handler.go b/dot/core/mocks/digest_handler.go index 77c8e1a76c..95dd03c168 100644 --- a/dot/core/mocks/digest_handler.go +++ b/dot/core/mocks/digest_handler.go @@ -1,6 +1,6 @@ // Code generated by mockery v2.8.0. DO NOT EDIT. -package core +package mocks import ( types "github.com/ChainSafe/gossamer/dot/types" diff --git a/dot/core/mocks/network.go b/dot/core/mocks/network.go index 573cb7ac78..314c19b80a 100644 --- a/dot/core/mocks/network.go +++ b/dot/core/mocks/network.go @@ -1,6 +1,6 @@ // Code generated by mockery v2.8.0. DO NOT EDIT. -package core +package mocks import ( network "github.com/ChainSafe/gossamer/dot/network" diff --git a/dot/core/service.go b/dot/core/service.go index f4db0e7fdb..f59f3bef92 100644 --- a/dot/core/service.go +++ b/dot/core/service.go @@ -461,6 +461,16 @@ func (s *Service) HasKey(pubKeyStr, keyType string) (bool, error) { return keystore.HasKey(pubKeyStr, keyType, s.keys.Acco) } +// DecodeSessionKeys executes the runtime DecodeSessionKeys and return the scale encoded keys +func (s *Service) DecodeSessionKeys(enc []byte) ([]byte, error) { + rt, err := s.blockState.GetRuntime(nil) + if err != nil { + return nil, err + } + + return rt.DecodeSessionKeys(enc) +} + // GetRuntimeVersion gets the current RuntimeVersion func (s *Service) GetRuntimeVersion(bhash *common.Hash) (runtime.Version, error) { var stateRootHash *common.Hash diff --git a/dot/core/service_test.go b/dot/core/service_test.go index 6eb5ecbb53..354552eaca 100644 --- a/dot/core/service_test.go +++ b/dot/core/service_test.go @@ -17,6 +17,7 @@ package core import ( + "errors" "io/ioutil" "math/big" "os" @@ -36,9 +37,12 @@ import ( "github.com/ChainSafe/gossamer/lib/transaction" "github.com/ChainSafe/gossamer/lib/utils" log "github.com/ChainSafe/log15" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "github.com/ChainSafe/gossamer/dot/core/mocks" coremocks "github.com/ChainSafe/gossamer/dot/core/mocks" + runtimemocks "github.com/ChainSafe/gossamer/lib/runtime/mocks" ) func addTestBlocksToState(t *testing.T, depth int, blockState BlockState) { @@ -614,3 +618,36 @@ func TestService_HandleRuntimeChangesAfterCodeSubstitutes(t *testing.T) { require.NotEqualf(t, codeHashBefore, rt.GetCodeHash(), "expected different code hash after runtime update") // codeHash should change after runtime change } + +func TestDecodeSessionKeys(t *testing.T) { + mockInstance := new(runtimemocks.MockInstance) + mockInstance.On("DecodeSessionKeys", mock.AnythingOfType("[]uint8")).Return([]byte{}, nil).Once() + + mockBlockState := new(mocks.MockBlockState) + mockBlockState.On("GetRuntime", mock.AnythingOfType("*common.Hash")).Return(mockInstance, nil).Once() + + coreservice := new(Service) + coreservice.blockState = mockBlockState + + b, err := coreservice.DecodeSessionKeys([]byte{}) + + mockBlockState.AssertCalled(t, "GetRuntime", mock.AnythingOfType("*common.Hash")) + mockInstance.AssertCalled(t, "DecodeSessionKeys", []uint8{}) + + require.NoError(t, err) + require.Equal(t, b, []byte{}) +} + +func TestDecodeSessionKeys_WhenGetRuntimeReturnError(t *testing.T) { + mockBlockState := new(mocks.MockBlockState) + mockBlockState.On("GetRuntime", mock.AnythingOfType("*common.Hash")).Return(nil, errors.New("problems")).Once() + + coreservice := new(Service) + coreservice.blockState = mockBlockState + + b, err := coreservice.DecodeSessionKeys([]byte{}) + + mockBlockState.AssertCalled(t, "GetRuntime", mock.AnythingOfType("*common.Hash")) + require.Error(t, err, "problems") + require.Nil(t, b) +} diff --git a/dot/rpc/modules/api.go b/dot/rpc/modules/api.go index 833ad231cc..36d497758b 100644 --- a/dot/rpc/modules/api.go +++ b/dot/rpc/modules/api.go @@ -77,6 +77,7 @@ type CoreAPI interface { GetRuntimeVersion(bhash *common.Hash) (runtime.Version, error) HandleSubmittedExtrinsic(types.Extrinsic) error GetMetadata(bhash *common.Hash) ([]byte, error) + DecodeSessionKeys(enc []byte) ([]byte, error) } // RPCAPI is the interface for methods related to RPC service diff --git a/dot/rpc/modules/author.go b/dot/rpc/modules/author.go index c0dd36079d..51394a2bab 100644 --- a/dot/rpc/modules/author.go +++ b/dot/rpc/modules/author.go @@ -17,13 +17,14 @@ package modules import ( - "fmt" + "errors" "net/http" - "reflect" + "strings" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/keystore" + "github.com/ChainSafe/gossamer/pkg/scale" log "github.com/ChainSafe/log15" ) @@ -35,8 +36,17 @@ type AuthorModule struct { txStateAPI TransactionStateAPI } +// HasSessionKeyRequest is used to receive the rpc data +type HasSessionKeyRequest struct { + PublicKeys string +} + // KeyInsertRequest is used as model for the JSON -type KeyInsertRequest []string +type KeyInsertRequest struct { + Type string + Seed string + PublicKey string +} // Extrinsic represents a hex-encoded extrinsic type Extrinsic struct { @@ -64,6 +74,18 @@ type RemoveExtrinsicsResponse []common.Hash // KeyRotateResponse is a byte array used to rotate type KeyRotateResponse []byte +// HasSessionKeyResponse is the response to the RPC call author_hasSessionKeys +type HasSessionKeyResponse bool + +// KeyTypeID represents the key type of a session key +type keyTypeID [4]uint8 + +// DecodedKey is the representation of a scaled decoded public key +type decodedKey struct { + Data []uint8 + Type keyTypeID +} + // ExtrinsicStatus holds the actual valid statuses type ExtrinsicStatus struct { IsFuture bool @@ -94,27 +116,66 @@ func NewAuthorModule(logger log.Logger, coreAPI CoreAPI, txStateAPI TransactionS } } -// InsertKey inserts a key into the keystore -func (am *AuthorModule) InsertKey(r *http.Request, req *KeyInsertRequest, res *KeyInsertResponse) error { - keyReq := *req +// HasSessionKeys checks if the keystore has private keys for the given session public keys. +func (am *AuthorModule) HasSessionKeys(r *http.Request, req *HasSessionKeyRequest, res *HasSessionKeyResponse) error { + pubKeysBytes, err := common.HexToBytes(req.PublicKeys) + if err != nil { + return err + } + + pkeys, err := scale.Marshal(pubKeysBytes) + if err != nil { + return err + } - pkDec, err := common.HexToBytes(keyReq[1]) + data, err := am.coreAPI.DecodeSessionKeys(pkeys) if err != nil { + *res = false return err } - privateKey, err := keystore.DecodePrivateKey(pkDec, keystore.DetermineKeyType(keyReq[0])) + var decodedKeys *[]decodedKey + err = scale.Unmarshal(data, &decodedKeys) + if err != nil { + return err + } + + if decodedKeys == nil || len(*decodedKeys) < 1 { + *res = false + return nil + } + + for _, key := range *decodedKeys { + encType := keystore.Name(key.Type[:]) + ok, err := am.coreAPI.HasKey(common.BytesToHex(key.Data), string(encType)) + + if err != nil || !ok { + *res = false + return err + } + } + + *res = true + return nil +} + +// InsertKey inserts a key into the keystore +func (am *AuthorModule) InsertKey(r *http.Request, req *KeyInsertRequest, res *KeyInsertResponse) error { + keyReq := *req + + keyBytes, err := common.HexToBytes(req.Seed) if err != nil { return err } - keyPair, err := keystore.PrivateKeyToKeypair(privateKey) + keyPair, err := keystore.DecodeKeyPairFromHex(keyBytes, keystore.DetermineKeyType(keyReq.Type)) if err != nil { return err } - if !reflect.DeepEqual(keyPair.Public().Hex(), keyReq[2]) { - return fmt.Errorf("generated public key does not equal provide public key") + //strings.EqualFold compare using case-insensitivity. + if !strings.EqualFold(keyPair.Public().Hex(), keyReq.PublicKey) { + return errors.New("generated public key does not equal provide public key") } am.coreAPI.InsertKey(keyPair) diff --git a/dot/rpc/modules/author_test.go b/dot/rpc/modules/author_test.go index 4de36c2b33..2d84d779ec 100644 --- a/dot/rpc/modules/author_test.go +++ b/dot/rpc/modules/author_test.go @@ -1,6 +1,7 @@ package modules import ( + "errors" "fmt" "net/http" "testing" @@ -8,13 +9,160 @@ import ( apimocks "github.com/ChainSafe/gossamer/dot/rpc/modules/mocks" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/crypto/sr25519" "github.com/ChainSafe/gossamer/lib/keystore" + "github.com/ChainSafe/gossamer/lib/runtime" + "github.com/ChainSafe/gossamer/lib/runtime/wasmer" "github.com/ChainSafe/gossamer/lib/transaction" log "github.com/ChainSafe/log15" "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" ) +func TestAuthorModule_HasSessionKey_WhenScaleDataEmptyOrNil(t *testing.T) { + keys := "0x00" + runtimeInstance := wasmer.NewTestInstance(t, runtime.NODE_RUNTIME) + + coremockapi := new(apimocks.MockCoreAPI) + + decodeSessionKeysMock := coremockapi.On("DecodeSessionKeys", mock.AnythingOfType("[]uint8")) + decodeSessionKeysMock.Run(func(args mock.Arguments) { + b := args.Get(0).([]byte) + dec, err := runtimeInstance.DecodeSessionKeys(b) + decodeSessionKeysMock.ReturnArguments = []interface{}{dec, err} + }) + + module := &AuthorModule{ + coreAPI: coremockapi, + logger: log.New("service", "RPC", "module", "author"), + } + + req := &HasSessionKeyRequest{ + PublicKeys: keys, + } + + var res HasSessionKeyResponse + err := module.HasSessionKeys(nil, req, &res) + require.NoError(t, err) + require.False(t, bool(res)) + + coremockapi.AssertCalled(t, "DecodeSessionKeys", mock.AnythingOfType("[]uint8")) +} + +func TestAuthorModule_HasSessionKey_WhenRuntimeFails(t *testing.T) { + coremockapi := new(apimocks.MockCoreAPI) + coremockapi.On("DecodeSessionKeys", mock.AnythingOfType("[]uint8")).Return(nil, errors.New("problems with runtime")) + + module := &AuthorModule{ + coreAPI: coremockapi, + logger: log.New("service", "RPC", "module", "author"), + } + + req := &HasSessionKeyRequest{ + PublicKeys: "0x00", + } + + var res HasSessionKeyResponse + err := module.HasSessionKeys(nil, req, &res) + require.Error(t, err, "problems with runtime") + require.False(t, bool(res)) +} + +func TestAuthorModule_HasSessionKey_WhenThereIsNoKeys(t *testing.T) { + keys := "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d34309a9d2a24213896ff06895db16aade8b6502f3a71cf56374cc3852042602634309a9d2a24213896ff06895db16aade8b6502f3a71cf56374cc3852042602634309a9d2a24213896ff06895db16aade8b6502f3a71cf56374cc38520426026" + runtimeInstance := wasmer.NewTestInstance(t, runtime.NODE_RUNTIME) + + coremockapi := new(apimocks.MockCoreAPI) + coremockapi.On("HasKey", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(false, nil) + + decodeSessionKeysMock := coremockapi.On("DecodeSessionKeys", mock.AnythingOfType("[]uint8")) + decodeSessionKeysMock.Run(func(args mock.Arguments) { + b := args.Get(0).([]byte) + dec, err := runtimeInstance.DecodeSessionKeys(b) + decodeSessionKeysMock.ReturnArguments = []interface{}{dec, err} + }) + + module := &AuthorModule{ + coreAPI: coremockapi, + logger: log.New("service", "RPC", "module", "author"), + } + + req := &HasSessionKeyRequest{ + PublicKeys: keys, + } + + var res HasSessionKeyResponse + err := module.HasSessionKeys(nil, req, &res) + require.NoError(t, err) + require.False(t, bool(res)) + + coremockapi.AssertCalled(t, "DecodeSessionKeys", mock.AnythingOfType("[]uint8")) + coremockapi.AssertCalled(t, "HasKey", mock.AnythingOfType("string"), mock.AnythingOfType("string")) + coremockapi.AssertNumberOfCalls(t, "HasKey", 1) +} + +func TestAuthorModule_HasSessionKey(t *testing.T) { + globalStore := keystore.NewGlobalKeystore() + + coremockapi := new(apimocks.MockCoreAPI) + mockInsertKey := coremockapi.On("InsertKey", mock.AnythingOfType("*sr25519.Keypair")) + mockInsertKey.Run(func(args mock.Arguments) { + kp := args.Get(0).(*sr25519.Keypair) + globalStore.Acco.Insert(kp) + }) + + mockHasKey := coremockapi.On("HasKey", mock.AnythingOfType("string"), mock.AnythingOfType("string")) + mockHasKey.Run(func(args mock.Arguments) { + pubKeyHex := args.Get(0).(string) + keyType := args.Get(1).(string) + + ok, err := keystore.HasKey(pubKeyHex, keyType, globalStore.Acco) + mockHasKey.ReturnArguments = []interface{}{ok, err} + }) + + keys := "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d34309a9d2a24213896ff06895db16aade8b6502f3a71cf56374cc3852042602634309a9d2a24213896ff06895db16aade8b6502f3a71cf56374cc3852042602634309a9d2a24213896ff06895db16aade8b6502f3a71cf56374cc38520426026" + runtimeInstance := wasmer.NewTestInstance(t, runtime.NODE_RUNTIME) + + decodeSessionKeysMock := coremockapi.On("DecodeSessionKeys", mock.AnythingOfType("[]uint8")) + decodeSessionKeysMock.Run(func(args mock.Arguments) { + b := args.Get(0).([]byte) + dec, err := runtimeInstance.DecodeSessionKeys(b) + decodeSessionKeysMock.ReturnArguments = []interface{}{dec, err} + }) + + module := &AuthorModule{ + coreAPI: coremockapi, + logger: log.New("service", "RPC", "module", "author"), + } + + req := &HasSessionKeyRequest{ + PublicKeys: keys, + } + + err := module.InsertKey(nil, &KeyInsertRequest{ + Type: "babe", + Seed: "0xfec0f475b818470af5caf1f3c1b1558729961161946d581d2755f9fb566534f8", + PublicKey: "0x34309a9d2a24213896ff06895db16aade8b6502f3a71cf56374cc38520426026", + }, nil) + coremockapi.AssertCalled(t, "InsertKey", mock.AnythingOfType("*sr25519.Keypair")) + require.NoError(t, err) + require.Equal(t, 1, globalStore.Acco.Size()) + + err = module.InsertKey(nil, &KeyInsertRequest{ + Type: "babe", + Seed: "0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a", + PublicKey: "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + }, nil) + require.NoError(t, err) + require.Equal(t, 2, globalStore.Acco.Size()) + + var res HasSessionKeyResponse + err = module.HasSessionKeys(nil, req, &res) + require.NoError(t, err) + require.True(t, bool(res)) +} + func TestAuthorModule_SubmitExtrinsic(t *testing.T) { errMockCoreAPI := &apimocks.MockCoreAPI{} errMockCoreAPI.On("HandleSubmittedExtrinsic", mock.AnythingOfType("types.Extrinsic")).Return(fmt.Errorf("some error")) @@ -202,8 +350,8 @@ func TestAuthorModule_InsertKey(t *testing.T) { args: args{ req: &KeyInsertRequest{ "babe", - "0xb7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309", "0x6246ddf254e0b4b4e7dffefc8adf69d212b98ac2b579c362b473fec8c40b4c0a", + "0xdad5131003242c37c227f744f82118dd59a24b949ae264a93d949100738c196c", }, }, }, @@ -214,9 +362,10 @@ func TestAuthorModule_InsertKey(t *testing.T) { coreAPI: mockCoreAPI, }, args: args{ - req: &KeyInsertRequest{"gran", - "0xb7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309b7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309", - "0xb7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309", + req: &KeyInsertRequest{ + "gran", + "0xb48004c6e1625282313b07d1c9950935e86894a2e4f21fb1ffee9854d180c781", + "0xa7d6507d59f8871b8f1a0f2c32e219adfacff4c9fcb05b0b2d8ebd6a65c88ee6", }, }, }, diff --git a/dot/rpc/modules/mocks/core_api.go b/dot/rpc/modules/mocks/core_api.go index 380de34353..beda95a749 100644 --- a/dot/rpc/modules/mocks/core_api.go +++ b/dot/rpc/modules/mocks/core_api.go @@ -18,6 +18,29 @@ type MockCoreAPI struct { mock.Mock } +// DecodeSessionKeys provides a mock function with given fields: enc +func (_m *MockCoreAPI) DecodeSessionKeys(enc []byte) ([]byte, error) { + ret := _m.Called(enc) + + var r0 []byte + if rf, ok := ret.Get(0).(func([]byte) []byte); ok { + r0 = rf(enc) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func([]byte) error); ok { + r1 = rf(enc) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetMetadata provides a mock function with given fields: bhash func (_m *MockCoreAPI) GetMetadata(bhash *common.Hash) ([]byte, error) { ret := _m.Called(bhash) @@ -103,17 +126,3 @@ func (_m *MockCoreAPI) HasKey(pubKeyStr string, keyType string) (bool, error) { func (_m *MockCoreAPI) InsertKey(kp crypto.Keypair) { _m.Called(kp) } - -// IsBlockProducer provides a mock function with given fields: -func (_m *MockCoreAPI) IsBlockProducer() bool { - ret := _m.Called() - - var r0 bool - if rf, ok := ret.Get(0).(func() bool); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(bool) - } - - return r0 -} diff --git a/dot/rpc/service_test.go b/dot/rpc/service_test.go index 64e95f4ee8..1661028388 100644 --- a/dot/rpc/service_test.go +++ b/dot/rpc/service_test.go @@ -35,7 +35,7 @@ func TestNewService(t *testing.T) { func TestService_Methods(t *testing.T) { qtySystemMethods := 13 qtyRPCMethods := 1 - qtyAuthorMethods := 7 + qtyAuthorMethods := 8 rpcService := NewService() sysMod := modules.NewSystemModule(nil, nil, nil, nil, nil, nil) diff --git a/go.sum b/go.sum index c11637e1b0..91273a38f8 100644 --- a/go.sum +++ b/go.sum @@ -274,7 +274,6 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= diff --git a/lib/crypto/sr25519/sr25519.go b/lib/crypto/sr25519/sr25519.go index 157faa4dec..a9beb284a3 100644 --- a/lib/crypto/sr25519/sr25519.go +++ b/lib/crypto/sr25519/sr25519.go @@ -83,9 +83,13 @@ func NewKeypairFromPrivate(priv *PrivateKey) (*Keypair, error) { } // NewKeypairFromSeed returns a new sr25519 Keypair given a seed -func NewKeypairFromSeed(seed []byte) (*Keypair, error) { +func NewKeypairFromSeed(keystr []byte) (*Keypair, error) { + if len(keystr) != SeedLength { + return nil, errors.New("cannot generate key from seed: seed is not 32 bytes long") + } + buf := [SeedLength]byte{} - copy(buf[:], seed) + copy(buf[:], keystr) msc, err := sr25519.NewMiniSecretKeyFromRaw(buf) if err != nil { return nil, err diff --git a/lib/crypto/sr25519/sr25519_test.go b/lib/crypto/sr25519/sr25519_test.go index 4ab9add81e..04d468b1e8 100644 --- a/lib/crypto/sr25519/sr25519_test.go +++ b/lib/crypto/sr25519/sr25519_test.go @@ -34,6 +34,13 @@ func TestNewKeypairFromSeed(t *testing.T) { require.NoError(t, err) require.NotNil(t, kp.public) require.NotNil(t, kp.private) + + seed = make([]byte, 20) + _, err = rand.Read(seed) + require.NoError(t, err) + kp, err = NewKeypairFromSeed(seed) + require.Nil(t, kp) + require.Error(t, err, "cannot generate key from seed: seed is not 32 bytes long") } func TestSignAndVerify(t *testing.T) { diff --git a/lib/keystore/helpers.go b/lib/keystore/helpers.go index 0cc862ff29..576be2b930 100644 --- a/lib/keystore/helpers.go +++ b/lib/keystore/helpers.go @@ -64,6 +64,20 @@ func DecodePrivateKey(in []byte, keytype crypto.KeyType) (priv crypto.PrivateKey return priv, err } +// DecodeKeyPairFromHex turns an hex-encoded private key into a keypair +func DecodeKeyPairFromHex(keystr []byte, keytype crypto.KeyType) (kp crypto.Keypair, err error) { + switch keytype { + case crypto.Sr25519Type: + kp, err = sr25519.NewKeypairFromSeed(keystr) + case crypto.Ed25519Type: + kp, err = ed25519.NewKeypairFromSeed(keystr) + default: + return nil, errors.New("cannot decode key: invalid key type") + } + + return kp, err +} + // GenerateKeypair create a new keypair with the corresponding type and saves // it to basepath/keystore/[public key].key in json format encrypted using the // specified password and returns the resulting filepath of the new key diff --git a/lib/keystore/helpers_test.go b/lib/keystore/helpers_test.go index 6d23200dc6..71192a3eba 100644 --- a/lib/keystore/helpers_test.go +++ b/lib/keystore/helpers_test.go @@ -26,7 +26,10 @@ import ( "strings" "testing" + "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/crypto" + "github.com/ChainSafe/gossamer/lib/crypto/ed25519" + "github.com/ChainSafe/gossamer/lib/crypto/sr25519" "github.com/ChainSafe/gossamer/lib/utils" "github.com/stretchr/testify/require" @@ -369,3 +372,32 @@ func TestImportRawPrivateKey_Secp256k1(t *testing.T) { require.Equal(t, "secp256k1", kscontents.Type) require.Equal(t, "0x03409094a319b2961660c3ebcc7d206266182c1b3e60d341b5fb17e6851865825c", kscontents.PublicKey) } + +func TestDecodeKeyPairFromHex(t *testing.T) { + keytype := DetermineKeyType("babe") + seed := "0xfec0f475b818470af5caf1f3c1b1558729961161946d581d2755f9fb566534f8" + + keyBytes, err := common.HexToBytes(seed) + require.NoError(t, err) + + kp, err := DecodeKeyPairFromHex(keyBytes, keytype) + require.NoError(t, err) + require.IsType(t, &sr25519.Keypair{}, kp) + + expectedPublic := "0x34309a9d2a24213896ff06895db16aade8b6502f3a71cf56374cc38520426026" + require.Equal(t, kp.Public().Hex(), expectedPublic) + + keytype = DetermineKeyType("gran") + keyBytes, err = common.HexToBytes("0x9d96ebdb66b7b6851d529f0c366393782baeba71732a59ce201ea80760d4d66c") + require.NoError(t, err) + + kp, err = DecodeKeyPairFromHex(keyBytes, keytype) + require.NoError(t, err) + require.IsType(t, &ed25519.Keypair{}, kp) + + expectedPublic = "0xd3db685ed1f94c195dc3e72803fa3d8549df45381388e313fa8170f0b397895c" + require.Equal(t, kp.Public().Hex(), expectedPublic) + + _, err = DecodeKeyPairFromHex(nil, "") + require.Error(t, err, "cannot decode key: invalid key type") +} diff --git a/lib/runtime/constants.go b/lib/runtime/constants.go index 1a889a5d70..a691a7fc67 100644 --- a/lib/runtime/constants.go +++ b/lib/runtime/constants.go @@ -72,6 +72,8 @@ var ( BlockBuilderApplyExtrinsic = "BlockBuilder_apply_extrinsic" // BlockBuilderFinalizeBlock is the runtime API call BlockBuilder_finalize_block BlockBuilderFinalizeBlock = "BlockBuilder_finalize_block" + // DecodeSessionKeys is the runtime API call SessionKeys_decode_session_keys + DecodeSessionKeys = "SessionKeys_decode_session_keys" ) // GrandpaAuthoritiesKey is the location of GRANDPA authority data in the storage trie for LEGACY_NODE_RUNTIME and NODE_RUNTIME diff --git a/lib/runtime/interface.go b/lib/runtime/interface.go index 5b9500e64f..8ace2d1e9a 100644 --- a/lib/runtime/interface.go +++ b/lib/runtime/interface.go @@ -47,6 +47,7 @@ type Instance interface { ApplyExtrinsic(data types.Extrinsic) ([]byte, error) FinalizeBlock() (*types.Header, error) ExecuteBlock(block *types.Block) ([]byte, error) + DecodeSessionKeys(enc []byte) ([]byte, error) // TODO: parameters and return values for these are undefined in the spec CheckInherents() diff --git a/lib/runtime/life/exports.go b/lib/runtime/life/exports.go index cc04df8112..b0ef1b38f3 100644 --- a/lib/runtime/life/exports.go +++ b/lib/runtime/life/exports.go @@ -150,6 +150,11 @@ func (in *Instance) ExecuteBlock(block *types.Block) ([]byte, error) { return in.Exec(runtime.CoreExecuteBlock, bdEnc) } +// DecodeSessionKeys decodes the given public session keys. Returns a list of raw public keys including their key type. +func (in *Instance) DecodeSessionKeys(enc []byte) ([]byte, error) { + return in.Exec(runtime.DecodeSessionKeys, enc) +} + func (in *Instance) CheckInherents() {} //nolint func (in *Instance) RandomSeed() {} //nolint func (in *Instance) OffchainWorker() {} //nolint diff --git a/lib/runtime/mocks/instance.go b/lib/runtime/mocks/instance.go new file mode 100644 index 0000000000..43f873ce82 --- /dev/null +++ b/lib/runtime/mocks/instance.go @@ -0,0 +1,431 @@ +// Code generated by mockery v2.8.0. DO NOT EDIT. + +package mocks + +import ( + common "github.com/ChainSafe/gossamer/lib/common" + keystore "github.com/ChainSafe/gossamer/lib/keystore" + + mock "github.com/stretchr/testify/mock" + + runtime "github.com/ChainSafe/gossamer/lib/runtime" + + transaction "github.com/ChainSafe/gossamer/lib/transaction" + + types "github.com/ChainSafe/gossamer/dot/types" +) + +// MockInstance is an autogenerated mock type for the Instance type +type MockInstance struct { + mock.Mock +} + +// ApplyExtrinsic provides a mock function with given fields: data +func (_m *MockInstance) ApplyExtrinsic(data types.Extrinsic) ([]byte, error) { + ret := _m.Called(data) + + var r0 []byte + if rf, ok := ret.Get(0).(func(types.Extrinsic) []byte); ok { + r0 = rf(data) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(types.Extrinsic) error); ok { + r1 = rf(data) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BabeConfiguration provides a mock function with given fields: +func (_m *MockInstance) BabeConfiguration() (*types.BabeConfiguration, error) { + ret := _m.Called() + + var r0 *types.BabeConfiguration + if rf, ok := ret.Get(0).(func() *types.BabeConfiguration); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.BabeConfiguration) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CheckInherents provides a mock function with given fields: +func (_m *MockInstance) CheckInherents() { + _m.Called() +} + +// CheckRuntimeVersion provides a mock function with given fields: _a0 +func (_m *MockInstance) CheckRuntimeVersion(_a0 []byte) (runtime.Version, error) { + ret := _m.Called(_a0) + + var r0 runtime.Version + if rf, ok := ret.Get(0).(func([]byte) runtime.Version); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(runtime.Version) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func([]byte) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DecodeSessionKeys provides a mock function with given fields: enc +func (_m *MockInstance) DecodeSessionKeys(enc []byte) ([]byte, error) { + ret := _m.Called(enc) + + var r0 []byte + if rf, ok := ret.Get(0).(func([]byte) []byte); ok { + r0 = rf(enc) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func([]byte) error); ok { + r1 = rf(enc) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Exec provides a mock function with given fields: function, data +func (_m *MockInstance) Exec(function string, data []byte) ([]byte, error) { + ret := _m.Called(function, data) + + var r0 []byte + if rf, ok := ret.Get(0).(func(string, []byte) []byte); ok { + r0 = rf(function, data) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, []byte) error); ok { + r1 = rf(function, data) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ExecuteBlock provides a mock function with given fields: block +func (_m *MockInstance) ExecuteBlock(block *types.Block) ([]byte, error) { + ret := _m.Called(block) + + var r0 []byte + if rf, ok := ret.Get(0).(func(*types.Block) []byte); ok { + r0 = rf(block) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(*types.Block) error); ok { + r1 = rf(block) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FinalizeBlock provides a mock function with given fields: +func (_m *MockInstance) FinalizeBlock() (*types.Header, error) { + ret := _m.Called() + + var r0 *types.Header + if rf, ok := ret.Get(0).(func() *types.Header); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Header) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GenerateSessionKeys provides a mock function with given fields: +func (_m *MockInstance) GenerateSessionKeys() { + _m.Called() +} + +// GetCodeHash provides a mock function with given fields: +func (_m *MockInstance) GetCodeHash() common.Hash { + ret := _m.Called() + + var r0 common.Hash + if rf, ok := ret.Get(0).(func() common.Hash); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(common.Hash) + } + } + + return r0 +} + +// GrandpaAuthorities provides a mock function with given fields: +func (_m *MockInstance) GrandpaAuthorities() ([]*types.Authority, error) { + ret := _m.Called() + + var r0 []*types.Authority + if rf, ok := ret.Get(0).(func() []*types.Authority); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*types.Authority) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// InherentExtrinsics provides a mock function with given fields: data +func (_m *MockInstance) InherentExtrinsics(data []byte) ([]byte, error) { + ret := _m.Called(data) + + var r0 []byte + if rf, ok := ret.Get(0).(func([]byte) []byte); ok { + r0 = rf(data) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func([]byte) error); ok { + r1 = rf(data) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// InitializeBlock provides a mock function with given fields: header +func (_m *MockInstance) InitializeBlock(header *types.Header) error { + ret := _m.Called(header) + + var r0 error + if rf, ok := ret.Get(0).(func(*types.Header) error); ok { + r0 = rf(header) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Keystore provides a mock function with given fields: +func (_m *MockInstance) Keystore() *keystore.GlobalKeystore { + ret := _m.Called() + + var r0 *keystore.GlobalKeystore + if rf, ok := ret.Get(0).(func() *keystore.GlobalKeystore); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*keystore.GlobalKeystore) + } + } + + return r0 +} + +// Metadata provides a mock function with given fields: +func (_m *MockInstance) Metadata() ([]byte, error) { + ret := _m.Called() + + var r0 []byte + if rf, ok := ret.Get(0).(func() []byte); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NetworkService provides a mock function with given fields: +func (_m *MockInstance) NetworkService() runtime.BasicNetwork { + ret := _m.Called() + + var r0 runtime.BasicNetwork + if rf, ok := ret.Get(0).(func() runtime.BasicNetwork); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(runtime.BasicNetwork) + } + } + + return r0 +} + +// NodeStorage provides a mock function with given fields: +func (_m *MockInstance) NodeStorage() runtime.NodeStorage { + ret := _m.Called() + + var r0 runtime.NodeStorage + if rf, ok := ret.Get(0).(func() runtime.NodeStorage); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(runtime.NodeStorage) + } + + return r0 +} + +// OffchainWorker provides a mock function with given fields: +func (_m *MockInstance) OffchainWorker() { + _m.Called() +} + +// RandomSeed provides a mock function with given fields: +func (_m *MockInstance) RandomSeed() { + _m.Called() +} + +// SetContextStorage provides a mock function with given fields: s +func (_m *MockInstance) SetContextStorage(s runtime.Storage) { + _m.Called(s) +} + +// Stop provides a mock function with given fields: +func (_m *MockInstance) Stop() { + _m.Called() +} + +// UpdateRuntimeCode provides a mock function with given fields: _a0 +func (_m *MockInstance) UpdateRuntimeCode(_a0 []byte) error { + ret := _m.Called(_a0) + + var r0 error + if rf, ok := ret.Get(0).(func([]byte) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ValidateTransaction provides a mock function with given fields: e +func (_m *MockInstance) ValidateTransaction(e types.Extrinsic) (*transaction.Validity, error) { + ret := _m.Called(e) + + var r0 *transaction.Validity + if rf, ok := ret.Get(0).(func(types.Extrinsic) *transaction.Validity); ok { + r0 = rf(e) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*transaction.Validity) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(types.Extrinsic) error); ok { + r1 = rf(e) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Validator provides a mock function with given fields: +func (_m *MockInstance) Validator() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// Version provides a mock function with given fields: +func (_m *MockInstance) Version() (runtime.Version, error) { + ret := _m.Called() + + var r0 runtime.Version + if rf, ok := ret.Get(0).(func() runtime.Version); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(runtime.Version) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/lib/runtime/wasmer/exports.go b/lib/runtime/wasmer/exports.go index bb23594b90..48368eafe9 100644 --- a/lib/runtime/wasmer/exports.go +++ b/lib/runtime/wasmer/exports.go @@ -174,6 +174,11 @@ func (in *Instance) ExecuteBlock(block *types.Block) ([]byte, error) { return in.exec(runtime.CoreExecuteBlock, bdEnc) } +// DecodeSessionKeys decodes the given public session keys. Returns a list of raw public keys including their key type. +func (in *Instance) DecodeSessionKeys(enc []byte) ([]byte, error) { + return in.exec(runtime.DecodeSessionKeys, enc) +} + func (in *Instance) CheckInherents() {} //nolint func (in *Instance) RandomSeed() {} //nolint func (in *Instance) OffchainWorker() {} //nolint diff --git a/lib/runtime/wasmer/exports_test.go b/lib/runtime/wasmer/exports_test.go index 4ed2523a5f..96e2198b83 100644 --- a/lib/runtime/wasmer/exports_test.go +++ b/lib/runtime/wasmer/exports_test.go @@ -1010,6 +1010,29 @@ func TestInstance_ExecuteBlock_PolkadotBlock1089328(t *testing.T) { require.NoError(t, err) } +func TestInstance_DecodeSessionKeys(t *testing.T) { + keys := "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d34309a9d2a24213896ff06895db16aade8b6502f3a71cf56374cc3852042602634309a9d2a24213896ff06895db16aade8b6502f3a71cf56374cc3852042602634309a9d2a24213896ff06895db16aade8b6502f3a71cf56374cc38520426026" + pubkeys, err := common.HexToBytes(keys) + require.NoError(t, err) + + pukeysBytes, err := scale.Marshal(pubkeys) + require.NoError(t, err) + + instance := NewTestInstance(t, runtime.NODE_RUNTIME_v098) + decoded, err := instance.DecodeSessionKeys(pukeysBytes) + require.NoError(t, err) + + var decodedKeys *[]struct { + Data []uint8 + Type [4]uint8 + } + + err = scale.Unmarshal(decoded, &decodedKeys) + require.NoError(t, err) + + require.Len(t, *decodedKeys, 4) +} + func newTrieFromPairs(t *testing.T, filename string) *trie.Trie { data, err := ioutil.ReadFile(filename) require.NoError(t, err) diff --git a/lib/runtime/wasmtime/exports.go b/lib/runtime/wasmtime/exports.go index e2574f53c1..dc6f3a6f61 100644 --- a/lib/runtime/wasmtime/exports.go +++ b/lib/runtime/wasmtime/exports.go @@ -150,6 +150,11 @@ func (in *Instance) ExecuteBlock(block *types.Block) ([]byte, error) { return in.exec(runtime.CoreExecuteBlock, bdEnc) } +// DecodeSessionKeys decodes the given public session keys. Returns a list of raw public keys including their key type. +func (in *Instance) DecodeSessionKeys(enc []byte) ([]byte, error) { + return in.exec(runtime.DecodeSessionKeys, enc) +} + func (in *Instance) CheckInherents() {} //nolint func (in *Instance) RandomSeed() {} //nolint func (in *Instance) OffchainWorker() {} //nolint