Expose status specific methods (login/signup/joinpublicchannel) through the RPC api (#877)
This commit is contained in:
parent
953c26e8cf
commit
8c9db81bec
1
Makefile
1
Makefile
|
@ -116,6 +116,7 @@ mock: ##@other Regenerate mocks
|
|||
mockgen -package=fake -destination=geth/transactions/fake/mock.go -source=geth/transactions/fake/txservice.go
|
||||
mockgen -package=account -destination=geth/account/accounts_mock.go -source=geth/account/accounts.go
|
||||
mockgen -package=jail -destination=geth/jail/cell_mock.go -source=geth/jail/cell.go
|
||||
mockgen -package=status -destination=services/status/account_mock.go -source=services/status/service.go
|
||||
|
||||
docker-test: ##@tests Run tests in a docker container with golang.
|
||||
docker run --privileged --rm -it -v "$(shell pwd):$(DOCKER_TEST_WORKDIR)" -w "$(DOCKER_TEST_WORKDIR)" $(DOCKER_TEST_IMAGE) go test ${ARGS}
|
||||
|
|
|
@ -150,6 +150,10 @@ func (b *StatusBackend) startNode(config *params.NodeConfig) (err error) {
|
|||
}
|
||||
b.log.Info("Account reselected")
|
||||
|
||||
if st, err := b.statusNode.StatusService(); err == nil {
|
||||
st.SetAccountManager(b.AccountManager())
|
||||
}
|
||||
|
||||
signal.SendNodeReady()
|
||||
|
||||
return nil
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
shhmetrics "github.com/status-im/status-go/metrics/whisper"
|
||||
"github.com/status-im/status-go/services/personal"
|
||||
"github.com/status-im/status-go/services/shhext"
|
||||
"github.com/status-im/status-go/services/status"
|
||||
)
|
||||
|
||||
// Errors related to node and services creation.
|
||||
|
@ -33,6 +34,7 @@ var (
|
|||
ErrWhisperServiceRegistrationFailure = errors.New("failed to register the Whisper service")
|
||||
ErrLightEthRegistrationFailure = errors.New("failed to register the LES service")
|
||||
ErrPersonalServiceRegistrationFailure = errors.New("failed to register the personal api service")
|
||||
ErrStatusServiceRegistrationFailure = errors.New("failed to register the Status service")
|
||||
)
|
||||
|
||||
// All general log messages in this package should be routed through this logger.
|
||||
|
@ -93,6 +95,11 @@ func MakeNode(config *params.NodeConfig) (*node.Node, error) {
|
|||
return nil, fmt.Errorf("%v: %v", ErrWhisperServiceRegistrationFailure, err)
|
||||
}
|
||||
|
||||
// start status service.
|
||||
if err := activateStatusService(stack, config); err != nil {
|
||||
return nil, fmt.Errorf("%v: %v", ErrStatusServiceRegistrationFailure, err)
|
||||
}
|
||||
|
||||
return stack, nil
|
||||
}
|
||||
|
||||
|
@ -163,6 +170,22 @@ func activatePersonalService(stack *node.Node, config *params.NodeConfig) error
|
|||
})
|
||||
}
|
||||
|
||||
func activateStatusService(stack *node.Node, config *params.NodeConfig) error {
|
||||
if !config.StatusServiceEnabled {
|
||||
logger.Info("Status service api is disabled")
|
||||
return nil
|
||||
}
|
||||
|
||||
return stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||
var whisper *whisper.Whisper
|
||||
if err := ctx.Service(&whisper); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
svc := status.New(whisper)
|
||||
return svc, nil
|
||||
})
|
||||
}
|
||||
|
||||
// activateShhService configures Whisper and adds it to the given node.
|
||||
func activateShhService(stack *node.Node, config *params.NodeConfig) (err error) {
|
||||
if config.WhisperConfig == nil || !config.WhisperConfig.Enabled {
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/status-im/status-go/geth/peers"
|
||||
"github.com/status-im/status-go/geth/rpc"
|
||||
"github.com/status-im/status-go/services/shhext"
|
||||
"github.com/status-im/status-go/services/status"
|
||||
)
|
||||
|
||||
// tickerResolution is the delta to check blockchain sync progress.
|
||||
|
@ -393,6 +394,19 @@ func (n *StatusNode) LightEthereumService() (l *les.LightEthereum, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// StatusService exposes reference to status service running on top of the node
|
||||
func (n *StatusNode) StatusService() (st *status.Service, err error) {
|
||||
n.mu.RLock()
|
||||
defer n.mu.RUnlock()
|
||||
|
||||
err = n.gethService(&st)
|
||||
if err == node.ErrServiceUnknown {
|
||||
err = ErrServiceUnknown
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// WhisperService exposes reference to Whisper service running on top of the node
|
||||
func (n *StatusNode) WhisperService() (w *whisper.Whisper, err error) {
|
||||
n.mu.RLock()
|
||||
|
|
|
@ -306,6 +306,9 @@ type NodeConfig struct {
|
|||
|
||||
RegisterTopics []discv5.Topic `json:"RegisterTopics"`
|
||||
RequireTopics map[discv5.Topic]Limits `json:"RequireTopics"`
|
||||
|
||||
// StatusServiceEnabled enables status service api
|
||||
StatusServiceEnabled bool
|
||||
}
|
||||
|
||||
// NewNodeConfig creates new node configuration object
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: services/status/service.go
|
||||
|
||||
// Package status is a generated GoMock package.
|
||||
package status
|
||||
|
||||
import (
|
||||
ecdsa "crypto/ecdsa"
|
||||
accounts "github.com/ethereum/go-ethereum/accounts"
|
||||
keystore "github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockWhisper is a mock of Whisper interface
|
||||
type MockWhisper struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockWhisperMockRecorder
|
||||
}
|
||||
|
||||
// MockWhisperMockRecorder is the mock recorder for MockWhisper
|
||||
type MockWhisperMockRecorder struct {
|
||||
mock *MockWhisper
|
||||
}
|
||||
|
||||
// NewMockWhisper creates a new mock instance
|
||||
func NewMockWhisper(ctrl *gomock.Controller) *MockWhisper {
|
||||
mock := &MockWhisper{ctrl: ctrl}
|
||||
mock.recorder = &MockWhisperMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockWhisper) EXPECT() *MockWhisperMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// AddKeyPair mocks base method
|
||||
func (m *MockWhisper) AddKeyPair(key *ecdsa.PrivateKey) (string, error) {
|
||||
ret := m.ctrl.Call(m, "AddKeyPair", key)
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// AddKeyPair indicates an expected call of AddKeyPair
|
||||
func (mr *MockWhisperMockRecorder) AddKeyPair(key interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddKeyPair", reflect.TypeOf((*MockWhisper)(nil).AddKeyPair), key)
|
||||
}
|
||||
|
||||
// MockAccountManager is a mock of AccountManager interface
|
||||
type MockAccountManager struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockAccountManagerMockRecorder
|
||||
}
|
||||
|
||||
// MockAccountManagerMockRecorder is the mock recorder for MockAccountManager
|
||||
type MockAccountManagerMockRecorder struct {
|
||||
mock *MockAccountManager
|
||||
}
|
||||
|
||||
// NewMockAccountManager creates a new mock instance
|
||||
func NewMockAccountManager(ctrl *gomock.Controller) *MockAccountManager {
|
||||
mock := &MockAccountManager{ctrl: ctrl}
|
||||
mock.recorder = &MockAccountManagerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockAccountManager) EXPECT() *MockAccountManagerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// AddressToDecryptedAccount mocks base method
|
||||
func (m *MockAccountManager) AddressToDecryptedAccount(arg0, arg1 string) (accounts.Account, *keystore.Key, error) {
|
||||
ret := m.ctrl.Call(m, "AddressToDecryptedAccount", arg0, arg1)
|
||||
ret0, _ := ret[0].(accounts.Account)
|
||||
ret1, _ := ret[1].(*keystore.Key)
|
||||
ret2, _ := ret[2].(error)
|
||||
return ret0, ret1, ret2
|
||||
}
|
||||
|
||||
// AddressToDecryptedAccount indicates an expected call of AddressToDecryptedAccount
|
||||
func (mr *MockAccountManagerMockRecorder) AddressToDecryptedAccount(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddressToDecryptedAccount", reflect.TypeOf((*MockAccountManager)(nil).AddressToDecryptedAccount), arg0, arg1)
|
||||
}
|
||||
|
||||
// SelectAccount mocks base method
|
||||
func (m *MockAccountManager) SelectAccount(address, password string) error {
|
||||
ret := m.ctrl.Call(m, "SelectAccount", address, password)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SelectAccount indicates an expected call of SelectAccount
|
||||
func (mr *MockAccountManagerMockRecorder) SelectAccount(address, password interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectAccount", reflect.TypeOf((*MockAccountManager)(nil).SelectAccount), address, password)
|
||||
}
|
||||
|
||||
// CreateAccount mocks base method
|
||||
func (m *MockAccountManager) CreateAccount(password string) (string, string, string, error) {
|
||||
ret := m.ctrl.Call(m, "CreateAccount", password)
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(string)
|
||||
ret2, _ := ret[2].(string)
|
||||
ret3, _ := ret[3].(error)
|
||||
return ret0, ret1, ret2, ret3
|
||||
}
|
||||
|
||||
// CreateAccount indicates an expected call of CreateAccount
|
||||
func (mr *MockAccountManagerMockRecorder) CreateAccount(password interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateAccount", reflect.TypeOf((*MockAccountManager)(nil).CreateAccount), password)
|
||||
}
|
||||
|
||||
// MockWhisperService is a mock of WhisperService interface
|
||||
type MockWhisperService struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockWhisperServiceMockRecorder
|
||||
}
|
||||
|
||||
// MockWhisperServiceMockRecorder is the mock recorder for MockWhisperService
|
||||
type MockWhisperServiceMockRecorder struct {
|
||||
mock *MockWhisperService
|
||||
}
|
||||
|
||||
// NewMockWhisperService creates a new mock instance
|
||||
func NewMockWhisperService(ctrl *gomock.Controller) *MockWhisperService {
|
||||
mock := &MockWhisperService{ctrl: ctrl}
|
||||
mock.recorder = &MockWhisperServiceMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockWhisperService) EXPECT() *MockWhisperServiceMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// AddressToDecryptedAccount mocks base method
|
||||
func (m *MockWhisperService) AddressToDecryptedAccount(arg0, arg1 string) (accounts.Account, *keystore.Key, error) {
|
||||
ret := m.ctrl.Call(m, "AddressToDecryptedAccount", arg0, arg1)
|
||||
ret0, _ := ret[0].(accounts.Account)
|
||||
ret1, _ := ret[1].(*keystore.Key)
|
||||
ret2, _ := ret[2].(error)
|
||||
return ret0, ret1, ret2
|
||||
}
|
||||
|
||||
// AddressToDecryptedAccount indicates an expected call of AddressToDecryptedAccount
|
||||
func (mr *MockWhisperServiceMockRecorder) AddressToDecryptedAccount(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddressToDecryptedAccount", reflect.TypeOf((*MockWhisperService)(nil).AddressToDecryptedAccount), arg0, arg1)
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package status
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// PublicAPI represents a set of APIs from the `web3.status` namespace.
|
||||
type PublicAPI struct {
|
||||
s *Service
|
||||
}
|
||||
|
||||
// NewAPI creates an instance of the status API.
|
||||
func NewAPI(s *Service) *PublicAPI {
|
||||
return &PublicAPI{s: s}
|
||||
}
|
||||
|
||||
// LoginRequest : json request for status_login.
|
||||
type LoginRequest struct {
|
||||
Addr string `json:"address"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// LoginResponse : json response returned by status_login.
|
||||
type LoginResponse struct {
|
||||
AddressKeyID string `json:"address_key_id"`
|
||||
}
|
||||
|
||||
// Login is an implementation of `status_login` or `web3.status.login` API
|
||||
func (api *PublicAPI) Login(context context.Context, req LoginRequest) (res LoginResponse, err error) {
|
||||
_, accountKey, err := api.s.am.AddressToDecryptedAccount(req.Addr, req.Password)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if res.AddressKeyID, err = api.s.w.AddKeyPair(accountKey.PrivateKey); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = api.s.am.SelectAccount(req.Addr, req.Password); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SignupRequest : json request for status_signup.
|
||||
type SignupRequest struct {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// SignupResponse : json response returned by status_signup.
|
||||
type SignupResponse struct {
|
||||
Address string `json:"address"`
|
||||
Pubkey string `json:"pubkey"`
|
||||
Mnemonic string `json:"mnemonic"`
|
||||
}
|
||||
|
||||
// Signup is an implementation of `status_signup` or `web3.status.signup` API
|
||||
func (api *PublicAPI) Signup(context context.Context, req SignupRequest) (res SignupResponse, err error) {
|
||||
if res.Address, res.Pubkey, res.Mnemonic, err = api.s.am.CreateAccount(req.Password); err != nil {
|
||||
err = errors.New("could not create the specified account : " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
package status
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
func TestStatusSuite(t *testing.T) {
|
||||
suite.Run(t, new(StatusSuite))
|
||||
}
|
||||
|
||||
type StatusSuite struct {
|
||||
suite.Suite
|
||||
am *MockAccountManager
|
||||
w *MockWhisper
|
||||
api *PublicAPI
|
||||
}
|
||||
|
||||
func (s *StatusSuite) SetupTest() {
|
||||
ctrl := gomock.NewController(s.T())
|
||||
s.am = NewMockAccountManager(ctrl)
|
||||
s.w = NewMockWhisper(ctrl)
|
||||
service := New(s.w)
|
||||
service.SetAccountManager(s.am)
|
||||
|
||||
s.api = NewAPI(service)
|
||||
}
|
||||
|
||||
var logintests = []struct {
|
||||
name string
|
||||
expectedAddressKey string
|
||||
expectedError error
|
||||
prepareExpectations func(*StatusSuite)
|
||||
}{
|
||||
{
|
||||
name: "success login",
|
||||
expectedAddressKey: "addressKey",
|
||||
expectedError: nil,
|
||||
prepareExpectations: func(s *StatusSuite) {
|
||||
key := keystore.Key{
|
||||
PrivateKey: &ecdsa.PrivateKey{},
|
||||
}
|
||||
s.am.EXPECT().AddressToDecryptedAccount("address...", "password").Return(accounts.Account{}, &key, nil)
|
||||
s.w.EXPECT().AddKeyPair(key.PrivateKey).Return("addressKey", nil)
|
||||
s.am.EXPECT().SelectAccount("address...", "password").Return(nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error when decrypting account from address",
|
||||
expectedAddressKey: "",
|
||||
expectedError: errors.New("foo"),
|
||||
prepareExpectations: func(s *StatusSuite) {
|
||||
key := keystore.Key{
|
||||
PrivateKey: &ecdsa.PrivateKey{},
|
||||
}
|
||||
s.am.EXPECT().AddressToDecryptedAccount("address...", "password").Return(accounts.Account{}, &key, errors.New("foo"))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error when adding key pair to whisper",
|
||||
expectedAddressKey: "",
|
||||
expectedError: errors.New("foo"),
|
||||
prepareExpectations: func(s *StatusSuite) {
|
||||
key := keystore.Key{
|
||||
PrivateKey: &ecdsa.PrivateKey{},
|
||||
}
|
||||
s.am.EXPECT().AddressToDecryptedAccount("address...", "password").Return(accounts.Account{}, &key, nil)
|
||||
s.w.EXPECT().AddKeyPair(key.PrivateKey).Return("", errors.New("foo"))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error when selecting account",
|
||||
expectedAddressKey: "",
|
||||
expectedError: errors.New("foo"),
|
||||
prepareExpectations: func(s *StatusSuite) {
|
||||
key := keystore.Key{
|
||||
PrivateKey: &ecdsa.PrivateKey{},
|
||||
}
|
||||
s.am.EXPECT().AddressToDecryptedAccount("address...", "password").Return(accounts.Account{}, &key, nil)
|
||||
s.w.EXPECT().AddKeyPair(key.PrivateKey).Return("", nil)
|
||||
s.am.EXPECT().SelectAccount("address...", "password").Return(errors.New("foo"))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func (s *StatusSuite) TestLogin() {
|
||||
for _, t := range logintests {
|
||||
req := LoginRequest{Addr: "address...", Password: "password"}
|
||||
|
||||
t.prepareExpectations(s)
|
||||
|
||||
var ctx context.Context
|
||||
res, err := s.api.Login(ctx, req)
|
||||
s.Equal(t.expectedAddressKey, res.AddressKeyID, "failed scenario : "+t.name)
|
||||
s.Equal(t.expectedError, err, "failed scenario : "+t.name)
|
||||
}
|
||||
}
|
||||
|
||||
var signuptests = []struct {
|
||||
name string
|
||||
expectedResponse SignupResponse
|
||||
expectedError error
|
||||
prepareExpectations func(*StatusSuite)
|
||||
}{
|
||||
{
|
||||
name: "success signup",
|
||||
expectedResponse: SignupResponse{
|
||||
Address: "addr",
|
||||
Pubkey: "pubkey",
|
||||
Mnemonic: "mnemonic",
|
||||
},
|
||||
expectedError: nil,
|
||||
prepareExpectations: func(s *StatusSuite) {
|
||||
s.am.EXPECT().CreateAccount("password").Return("addr", "pubkey", "mnemonic", nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "success signup",
|
||||
expectedResponse: SignupResponse{
|
||||
Address: "",
|
||||
Pubkey: "",
|
||||
Mnemonic: "",
|
||||
},
|
||||
expectedError: errors.New("could not create the specified account : foo"),
|
||||
prepareExpectations: func(s *StatusSuite) {
|
||||
s.am.EXPECT().CreateAccount("password").Return("", "", "", errors.New("foo"))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func (s *StatusSuite) TestSignup() {
|
||||
for _, t := range signuptests {
|
||||
t.prepareExpectations(s)
|
||||
|
||||
var ctx context.Context
|
||||
res, err := s.api.Signup(ctx, SignupRequest{Password: "password"})
|
||||
s.Equal(t.expectedResponse.Address, res.Address, "failed scenario : "+t.name)
|
||||
s.Equal(t.expectedResponse.Pubkey, res.Pubkey, "failed scenario : "+t.name)
|
||||
s.Equal(t.expectedResponse.Mnemonic, res.Mnemonic, "failed scenario : "+t.name)
|
||||
s.Equal(t.expectedError, err, "failed scenario : "+t.name)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package status
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// Make sure that Service implements node.Service interface.
|
||||
var _ node.Service = (*Service)(nil)
|
||||
|
||||
// WhisperService whisper interface to add key pairs
|
||||
type WhisperService interface {
|
||||
AddKeyPair(key *ecdsa.PrivateKey) (string, error)
|
||||
}
|
||||
|
||||
// AccountManager interface to manage account actions
|
||||
type AccountManager interface {
|
||||
AddressToDecryptedAccount(string, string) (accounts.Account, *keystore.Key, error)
|
||||
SelectAccount(address, password string) error
|
||||
CreateAccount(password string) (address, pubKey, mnemonic string, err error)
|
||||
}
|
||||
|
||||
// Service represents out own implementation of status status operations.
|
||||
type Service struct {
|
||||
am AccountManager
|
||||
w WhisperService
|
||||
}
|
||||
|
||||
// New returns a new Service.
|
||||
func New(w WhisperService) *Service {
|
||||
return &Service{w: w}
|
||||
}
|
||||
|
||||
// Protocols returns a new protocols list. In this case, there are none.
|
||||
func (s *Service) Protocols() []p2p.Protocol {
|
||||
return []p2p.Protocol{}
|
||||
}
|
||||
|
||||
// APIs returns a list of new APIs.
|
||||
func (s *Service) APIs() []rpc.API {
|
||||
|
||||
return []rpc.API{
|
||||
{
|
||||
Namespace: "status",
|
||||
Version: "1.0",
|
||||
Service: NewAPI(s),
|
||||
Public: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SetAccountManager sets account manager for the API calls.
|
||||
func (s *Service) SetAccountManager(a AccountManager) {
|
||||
s.am = a
|
||||
}
|
||||
|
||||
// Start is run when a service is started.
|
||||
// It does nothing in this case but is required by `node.Service` interface.
|
||||
func (s *Service) Start(server *p2p.Server) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop is run when a service is stopped.
|
||||
// It does nothing in this case but is required by `node.Service` interface.
|
||||
func (s *Service) Stop() error {
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package services
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/status-im/status-go/geth/api"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
"github.com/status-im/status-go/signal"
|
||||
"github.com/status-im/status-go/t/e2e"
|
||||
|
||||
. "github.com/status-im/status-go/t/utils"
|
||||
)
|
||||
|
||||
type BaseJSONRPCSuite struct {
|
||||
e2e.BackendTestSuite
|
||||
}
|
||||
|
||||
func (s *BaseJSONRPCSuite) AssertAPIMethodUnexported(method string) {
|
||||
exported := s.isMethodExported(method, false)
|
||||
s.False(exported,
|
||||
"method %s should be hidden, but it isn't",
|
||||
method)
|
||||
}
|
||||
|
||||
func (s *BaseJSONRPCSuite) AssertAPIMethodExported(method string) {
|
||||
exported := s.isMethodExported(method, false)
|
||||
s.True(exported,
|
||||
"method %s should be exported, but it isn't",
|
||||
method)
|
||||
}
|
||||
|
||||
func (s *BaseJSONRPCSuite) AssertAPIMethodExportedPrivately(method string) {
|
||||
exported := s.isMethodExported(method, true)
|
||||
s.True(exported,
|
||||
"method %s should be exported, but it isn't",
|
||||
method)
|
||||
}
|
||||
|
||||
func (s *BaseJSONRPCSuite) isMethodExported(method string, private bool) bool {
|
||||
var result string
|
||||
|
||||
cmd := fmt.Sprintf(`{"jsonrpc":"2.0", "method": "%s", "params": []}`, method)
|
||||
if private {
|
||||
result = s.Backend.CallPrivateRPC(cmd)
|
||||
} else {
|
||||
result = s.Backend.CallRPC(cmd)
|
||||
}
|
||||
|
||||
var response struct {
|
||||
Error *rpcError `json:"error"`
|
||||
}
|
||||
|
||||
s.NoError(json.Unmarshal([]byte(result), &response))
|
||||
|
||||
return !(response.Error != nil && response.Error.Code == methodNotFoundErrorCode)
|
||||
}
|
||||
|
||||
func (s *BaseJSONRPCSuite) SetupTest(upstreamEnabled bool, statusServiceEnabled bool) error {
|
||||
s.Backend = api.NewStatusBackend()
|
||||
s.NotNil(s.Backend)
|
||||
|
||||
nodeConfig, err := MakeTestNodeConfig(GetNetworkID())
|
||||
s.NoError(err)
|
||||
|
||||
nodeConfig.IPCEnabled = false
|
||||
nodeConfig.StatusServiceEnabled = statusServiceEnabled
|
||||
nodeConfig.HTTPHost = "" // to make sure that no HTTP interface is started
|
||||
|
||||
if upstreamEnabled {
|
||||
networkURL, err := GetRemoteURL()
|
||||
s.NoError(err)
|
||||
|
||||
nodeConfig.UpstreamConfig.Enabled = true
|
||||
nodeConfig.UpstreamConfig.URL = networkURL
|
||||
}
|
||||
|
||||
return s.Backend.StartNode(nodeConfig)
|
||||
}
|
||||
|
||||
func (s *BaseJSONRPCSuite) notificationHandler(account string, pass string, expectedError error) func(string) {
|
||||
return func(jsonEvent string) {
|
||||
envelope := unmarshalEnvelope(jsonEvent)
|
||||
if envelope.Type == signal.EventSignRequestAdded {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
id := event["id"].(string)
|
||||
s.T().Logf("Sign request added (will be completed shortly): {id: %s}\n", id)
|
||||
|
||||
//check for the correct method name
|
||||
method := event["method"].(string)
|
||||
s.Equal(params.PersonalSignMethodName, method)
|
||||
//check the event data
|
||||
args := event["args"].(map[string]interface{})
|
||||
s.Equal(signDataString, args["data"].(string))
|
||||
s.Equal(account, args["account"].(string))
|
||||
|
||||
e := s.Backend.ApproveSignRequest(id, pass).Error
|
||||
s.T().Logf("Sign request approved. {id: %s, acc: %s, err: %v}", id, account, e)
|
||||
if expectedError == nil {
|
||||
s.NoError(e, "cannot complete sign reauest[%v]: %v", id, e)
|
||||
} else {
|
||||
s.EqualError(e, expectedError.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,7 +11,6 @@ import (
|
|||
"github.com/status-im/status-go/geth/params"
|
||||
"github.com/status-im/status-go/services/personal"
|
||||
"github.com/status-im/status-go/signal"
|
||||
e2e "github.com/status-im/status-go/t/e2e"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
. "github.com/status-im/status-go/t/utils"
|
||||
|
@ -52,7 +51,7 @@ func TestPersonalSignSuiteUpstream(t *testing.T) {
|
|||
}
|
||||
|
||||
type PersonalSignSuite struct {
|
||||
e2e.BackendTestSuite
|
||||
BaseJSONRPCSuite
|
||||
upstream bool
|
||||
}
|
||||
|
||||
|
@ -62,40 +61,22 @@ func (s *PersonalSignSuite) TestRestrictedPersonalAPIs() {
|
|||
return
|
||||
}
|
||||
|
||||
err := s.initTest(s.upstream)
|
||||
err := s.SetupTest(s.upstream, false)
|
||||
s.NoError(err)
|
||||
defer func() {
|
||||
err := s.Backend.StopNode()
|
||||
s.NoError(err)
|
||||
}()
|
||||
// These personal APIs should be available
|
||||
s.testAPIExported("personal_sign", true)
|
||||
s.testAPIExported("personal_ecRecover", true)
|
||||
s.AssertAPIMethodExported("personal_sign")
|
||||
s.AssertAPIMethodExported("personal_ecRecover")
|
||||
// These personal APIs shouldn't be exported
|
||||
s.testAPIExported("personal_sendTransaction", false)
|
||||
s.testAPIExported("personal_unlockAccount", false)
|
||||
s.testAPIExported("personal_newAccount", false)
|
||||
s.testAPIExported("personal_lockAccount", false)
|
||||
s.testAPIExported("personal_listAccounts", false)
|
||||
s.testAPIExported("personal_importRawKey", false)
|
||||
}
|
||||
|
||||
func (s *PersonalSignSuite) testAPIExported(method string, expectExported bool) {
|
||||
cmd := fmt.Sprintf(`{"jsonrpc":"2.0", "method": "%s", "params": []}`, method)
|
||||
|
||||
result := s.Backend.CallRPC(cmd)
|
||||
|
||||
var response struct {
|
||||
Error *rpcError `json:"error"`
|
||||
}
|
||||
|
||||
s.NoError(json.Unmarshal([]byte(result), &response))
|
||||
|
||||
hidden := (response.Error != nil && response.Error.Code == methodNotFoundErrorCode)
|
||||
|
||||
s.Equal(expectExported, !hidden,
|
||||
"method %s should be %s, but it isn't",
|
||||
method, map[bool]string{true: "exported", false: "hidden"}[expectExported])
|
||||
s.AssertAPIMethodUnexported("personal_sendTransaction")
|
||||
s.AssertAPIMethodUnexported("personal_unlockAccount")
|
||||
s.AssertAPIMethodUnexported("personal_newAccount")
|
||||
s.AssertAPIMethodUnexported("personal_lockAccount")
|
||||
s.AssertAPIMethodUnexported("personal_listAccounts")
|
||||
s.AssertAPIMethodUnexported("personal_importRawKey")
|
||||
}
|
||||
|
||||
func (s *PersonalSignSuite) TestPersonalSignSuccess() {
|
||||
|
@ -141,12 +122,6 @@ func (s *PersonalSignSuite) TestPersonalSignNoAccountSelected() {
|
|||
}
|
||||
|
||||
// Utility methods
|
||||
func (s *PersonalSignSuite) notificationHandlerSuccess(account string, pass string) func(string) {
|
||||
return func(jsonEvent string) {
|
||||
s.notificationHandler(account, pass, nil)(jsonEvent)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PersonalSignSuite) notificationHandlerWrongPassword(account string, pass string) func(string) {
|
||||
return func(jsonEvent string) {
|
||||
s.notificationHandler(account, pass+"wrong", keystore.ErrDecrypt)(jsonEvent)
|
||||
|
@ -216,7 +191,7 @@ func (s *PersonalSignSuite) testPersonalSign(testParams testParams) string {
|
|||
testParams.HandlerFactory = s.notificationHandlerSuccess
|
||||
}
|
||||
|
||||
err := s.initTest(s.upstream)
|
||||
err := s.SetupTest(s.upstream, false)
|
||||
s.NoError(err)
|
||||
defer func() {
|
||||
err := s.Backend.StopNode()
|
||||
|
@ -246,6 +221,15 @@ func (s *PersonalSignSuite) testPersonalSign(testParams testParams) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (s *PersonalSignSuite) extractResultFromRPCResponse(response string) string {
|
||||
var r struct {
|
||||
Result string `json:"result"`
|
||||
}
|
||||
s.NoError(json.Unmarshal([]byte(response), &r))
|
||||
|
||||
return r.Result
|
||||
}
|
||||
|
||||
func unmarshalEnvelope(jsonEvent string) signal.Envelope {
|
||||
var envelope signal.Envelope
|
||||
if e := json.Unmarshal([]byte(jsonEvent), &envelope); e != nil {
|
||||
|
@ -268,7 +252,7 @@ func (s *PersonalSignSuite) TestPersonalRecoverSuccess() {
|
|||
return
|
||||
}
|
||||
|
||||
err := s.initTest(s.upstream)
|
||||
err := s.SetupTest(s.upstream, false)
|
||||
s.NoError(err)
|
||||
defer func() {
|
||||
err := s.Backend.StopNode()
|
||||
|
@ -288,29 +272,8 @@ func (s *PersonalSignSuite) TestPersonalRecoverSuccess() {
|
|||
s.True(strings.EqualFold(result, TestConfig.Account1.Address))
|
||||
}
|
||||
|
||||
func (s *PersonalSignSuite) initTest(upstreamEnabled bool) error {
|
||||
nodeConfig, err := MakeTestNodeConfig(GetNetworkID())
|
||||
s.NoError(err)
|
||||
|
||||
nodeConfig.IPCEnabled = false
|
||||
nodeConfig.HTTPHost = "" // to make sure that no HTTP interface is started
|
||||
|
||||
if upstreamEnabled {
|
||||
networkURL, err := GetRemoteURL()
|
||||
s.NoError(err)
|
||||
|
||||
nodeConfig.UpstreamConfig.Enabled = true
|
||||
nodeConfig.UpstreamConfig.URL = networkURL
|
||||
func (s *BaseJSONRPCSuite) notificationHandlerSuccess(account string, pass string) func(string) {
|
||||
return func(jsonEvent string) {
|
||||
s.notificationHandler(account, pass, nil)(jsonEvent)
|
||||
}
|
||||
|
||||
return s.Backend.StartNode(nodeConfig)
|
||||
}
|
||||
|
||||
func (s *PersonalSignSuite) extractResultFromRPCResponse(response string) string {
|
||||
var r struct {
|
||||
Result string `json:"result"`
|
||||
}
|
||||
s.NoError(json.Unmarshal([]byte(response), &r))
|
||||
|
||||
return r.Result
|
||||
}
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
package services
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/status-im/status-go/geth/account"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
"github.com/status-im/status-go/services/status"
|
||||
"github.com/status-im/status-go/signal"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
. "github.com/status-im/status-go/t/utils"
|
||||
)
|
||||
|
||||
type statusTestParams struct {
|
||||
Address string
|
||||
Password string
|
||||
HandlerFactory func(string, string) func(string)
|
||||
ExpectedError error
|
||||
ChannelName string
|
||||
}
|
||||
|
||||
func TestStatusAPISuite(t *testing.T) {
|
||||
s := new(StatusAPISuite)
|
||||
s.upstream = false
|
||||
suite.Run(t, s)
|
||||
}
|
||||
|
||||
func TestStatusAPISuiteUpstream(t *testing.T) {
|
||||
s := new(StatusAPISuite)
|
||||
s.upstream = true
|
||||
suite.Run(t, s)
|
||||
}
|
||||
|
||||
type StatusAPISuite struct {
|
||||
BaseJSONRPCSuite
|
||||
upstream bool
|
||||
}
|
||||
|
||||
func (s *StatusAPISuite) TestAccessibleStatusAPIs() {
|
||||
if s.upstream && GetNetworkID() == params.StatusChainNetworkID {
|
||||
s.T().Skip()
|
||||
return
|
||||
}
|
||||
|
||||
err := s.SetupTest(s.upstream, true)
|
||||
s.NoError(err)
|
||||
defer func() {
|
||||
err := s.Backend.StopNode()
|
||||
s.NoError(err)
|
||||
}()
|
||||
// These status APIs should be unavailable
|
||||
s.AssertAPIMethodUnexported("status_login")
|
||||
s.AssertAPIMethodUnexported("status_signup")
|
||||
|
||||
// These status APIs should be available only for IPC
|
||||
s.AssertAPIMethodExportedPrivately("status_login")
|
||||
s.AssertAPIMethodExportedPrivately("status_signup")
|
||||
}
|
||||
|
||||
func (s *StatusAPISuite) TestStatusLoginSuccess() {
|
||||
addressKeyID := s.testStatusLogin(statusTestParams{
|
||||
Address: TestConfig.Account1.Address,
|
||||
Password: TestConfig.Account1.Password,
|
||||
})
|
||||
s.NotEmpty(addressKeyID)
|
||||
}
|
||||
|
||||
func (s *StatusAPISuite) TestStatusLoginInvalidAddress() {
|
||||
s.testStatusLogin(statusTestParams{
|
||||
Address: "invalidaccount",
|
||||
Password: TestConfig.Account1.Password,
|
||||
ExpectedError: account.ErrAddressToAccountMappingFailure,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *StatusAPISuite) TestStatusLoginInvalidPassword() {
|
||||
s.testStatusLogin(statusTestParams{
|
||||
Address: "invalidaccount",
|
||||
Password: TestConfig.Account1.Password,
|
||||
ExpectedError: account.ErrAddressToAccountMappingFailure,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *StatusAPISuite) TestStatusSignupSuccess() {
|
||||
var pwd = "randompassword"
|
||||
|
||||
res := s.testStatusSignup(statusTestParams{
|
||||
Password: pwd,
|
||||
})
|
||||
s.NotEmpty(res.Address)
|
||||
s.NotEmpty(res.Pubkey)
|
||||
s.Equal(12, len(strings.Split(res.Mnemonic, " ")))
|
||||
|
||||
// I should be able to login with the newly created account
|
||||
_ = s.testStatusLogin(statusTestParams{
|
||||
Address: res.Address,
|
||||
Password: pwd,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *StatusAPISuite) testStatusLogin(testParams statusTestParams) *status.LoginResponse {
|
||||
// Test upstream if that's not StatusChain
|
||||
if s.upstream && GetNetworkID() == params.StatusChainNetworkID {
|
||||
s.T().Skip()
|
||||
return nil
|
||||
}
|
||||
|
||||
if testParams.HandlerFactory == nil {
|
||||
testParams.HandlerFactory = s.notificationHandlerSuccess
|
||||
}
|
||||
|
||||
err := s.SetupTest(s.upstream, true)
|
||||
s.NoError(err)
|
||||
defer func() {
|
||||
err := s.Backend.StopNode()
|
||||
s.NoError(err)
|
||||
}()
|
||||
|
||||
signal.SetDefaultNodeNotificationHandler(testParams.HandlerFactory(testParams.Address, testParams.Password))
|
||||
|
||||
req := status.LoginRequest{
|
||||
Addr: testParams.Address,
|
||||
Password: testParams.Password,
|
||||
}
|
||||
body, _ := json.Marshal(req)
|
||||
|
||||
basicCall := fmt.Sprintf(
|
||||
`{"jsonrpc":"2.0","method":"status_login","params":[%s],"id":67}`,
|
||||
body)
|
||||
|
||||
result := s.Backend.CallPrivateRPC(basicCall)
|
||||
if testParams.ExpectedError == nil {
|
||||
s.NotContains(result, "error")
|
||||
var r struct {
|
||||
Result *status.LoginResponse `json:"result"`
|
||||
}
|
||||
s.NoError(json.Unmarshal([]byte(result), &r))
|
||||
|
||||
return r.Result
|
||||
}
|
||||
|
||||
s.Contains(result, testParams.ExpectedError.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StatusAPISuite) testStatusSignup(testParams statusTestParams) *status.SignupResponse {
|
||||
// Test upstream if that's not StatusChain
|
||||
if s.upstream && GetNetworkID() == params.StatusChainNetworkID {
|
||||
s.T().Skip()
|
||||
return nil
|
||||
}
|
||||
|
||||
if testParams.HandlerFactory == nil {
|
||||
testParams.HandlerFactory = s.notificationHandlerSuccess
|
||||
}
|
||||
|
||||
err := s.SetupTest(s.upstream, true)
|
||||
s.NoError(err)
|
||||
defer func() {
|
||||
err := s.Backend.StopNode()
|
||||
s.NoError(err)
|
||||
}()
|
||||
|
||||
signal.SetDefaultNodeNotificationHandler(testParams.HandlerFactory(testParams.Address, testParams.Password))
|
||||
|
||||
req := status.SignupRequest{
|
||||
Password: testParams.Password,
|
||||
}
|
||||
body, _ := json.Marshal(req)
|
||||
|
||||
basicCall := fmt.Sprintf(
|
||||
`{"jsonrpc":"2.0","method":"status_signup","params":[%s],"id":67}`,
|
||||
body)
|
||||
|
||||
result := s.Backend.CallPrivateRPC(basicCall)
|
||||
|
||||
if testParams.ExpectedError == nil {
|
||||
s.NotContains(result, "error")
|
||||
var r struct {
|
||||
Result *status.SignupResponse `json:"result"`
|
||||
}
|
||||
s.NoError(json.Unmarshal([]byte(result), &r))
|
||||
|
||||
return r.Result
|
||||
}
|
||||
|
||||
s.Contains(result, testParams.ExpectedError.Error())
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue