Expose only public APIs in inproc RPC client [breaking-change] (#815)
`CallRPC` binding, which is used as a provider for web3.js, exposes only public or whitelisted APIs.
This commit is contained in:
parent
a7a2e01b4a
commit
0d652c3851
1
Makefile
1
Makefile
|
@ -112,7 +112,6 @@ mock-install: ##@other Install mocking tools
|
|||
go get -u github.com/golang/mock/mockgen
|
||||
|
||||
mock: ##@other Regenerate mocks
|
||||
mockgen -package=mailservice -destination=geth/mailservice/mailservice_mock.go github.com/status-im/status-go/geth/mailservice ServiceProvider
|
||||
mockgen -package=fcm -destination=geth/notifications/push/fcm/client_mock.go github.com/status-im/status-go/geth/notifications/push/fcm FirebaseClient,Notifier
|
||||
mockgen -package=fake -destination=geth/transactions/fake/mock.go github.com/status-im/status-go/geth/transactions/fake PublicTransactionPoolAPI
|
||||
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
diff --git a/node/node.go b/node/node.go
|
||||
index b02aecfa..7913e93d 100644
|
||||
--- a/node/node.go
|
||||
+++ b/node/node.go
|
||||
@@ -51,8 +51,9 @@ type Node struct {
|
||||
serviceFuncs []ServiceConstructor // Service constructors (in dependency order)
|
||||
services map[reflect.Type]Service // Currently running services
|
||||
|
||||
- rpcAPIs []rpc.API // List of APIs currently provided by the node
|
||||
- inprocHandler *rpc.Server // In-process RPC request handler to process the API requests
|
||||
+ rpcAPIs []rpc.API // List of APIs currently provided by the node
|
||||
+ inprocHandler *rpc.Server // In-process RPC request handler to process the API requests
|
||||
+ inprocPublicHandler *rpc.Server // In-process RPC request handler to process the public API requests
|
||||
|
||||
ipcEndpoint string // IPC endpoint to listen at (empty = IPC disabled)
|
||||
ipcListener net.Listener // IPC RPC listener socket to serve API requests
|
||||
@@ -259,18 +260,25 @@ func (n *Node) startRPC(services map[reflect.Type]Service) error {
|
||||
if err := n.startInProc(apis); err != nil {
|
||||
return err
|
||||
}
|
||||
+ if err := n.startPublicInProc(apis, n.config.HTTPModules); err != nil {
|
||||
+ n.stopInProc()
|
||||
+ return err
|
||||
+ }
|
||||
if err := n.startIPC(apis); err != nil {
|
||||
+ n.stopPublicInProc()
|
||||
n.stopInProc()
|
||||
return err
|
||||
}
|
||||
if err := n.startHTTP(n.httpEndpoint, apis, n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPVirtualHosts); err != nil {
|
||||
n.stopIPC()
|
||||
+ n.stopPublicInProc()
|
||||
n.stopInProc()
|
||||
return err
|
||||
}
|
||||
if err := n.startWS(n.wsEndpoint, apis, n.config.WSModules, n.config.WSOrigins, n.config.WSExposeAll); err != nil {
|
||||
n.stopHTTP()
|
||||
n.stopIPC()
|
||||
+ n.stopPublicInProc()
|
||||
n.stopInProc()
|
||||
return err
|
||||
}
|
||||
@@ -301,6 +309,36 @@ func (n *Node) stopInProc() {
|
||||
}
|
||||
}
|
||||
|
||||
+// startPublicInProc initializes an in-process RPC endpoint for public APIs.
|
||||
+func (n *Node) startPublicInProc(apis []rpc.API, modules []string) error {
|
||||
+ // Generate the whitelist based on the allowed modules
|
||||
+ whitelist := make(map[string]bool)
|
||||
+ for _, module := range modules {
|
||||
+ whitelist[module] = true
|
||||
+ }
|
||||
+
|
||||
+ // Register all the public APIs exposed by the services
|
||||
+ handler := rpc.NewServer()
|
||||
+ for _, api := range apis {
|
||||
+ if whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) {
|
||||
+ if err := handler.RegisterName(api.Namespace, api.Service); err != nil {
|
||||
+ return err
|
||||
+ }
|
||||
+ n.log.Debug("InProc public registered", "service", api.Service, "namespace", api.Namespace)
|
||||
+ }
|
||||
+ }
|
||||
+ n.inprocPublicHandler = handler
|
||||
+ return nil
|
||||
+}
|
||||
+
|
||||
+// stopPublicInProc terminates the in-process RPC endpoint for public APIs.
|
||||
+func (n *Node) stopPublicInProc() {
|
||||
+ if n.inprocPublicHandler != nil {
|
||||
+ n.inprocPublicHandler.Stop()
|
||||
+ n.inprocPublicHandler = nil
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
// startIPC initializes and starts the IPC RPC endpoint.
|
||||
func (n *Node) startIPC(apis []rpc.API) error {
|
||||
// Short circuit if the IPC endpoint isn't being exposed
|
||||
@@ -562,6 +600,18 @@ func (n *Node) Attach() (*rpc.Client, error) {
|
||||
return rpc.DialInProc(n.inprocHandler), nil
|
||||
}
|
||||
|
||||
+// AttachPublic creates an RPC client attached to an in-process Public API handler.
|
||||
+func (n *Node) AttachPublic() (*rpc.Client, error) {
|
||||
+ n.lock.RLock()
|
||||
+ defer n.lock.RUnlock()
|
||||
+
|
||||
+ if n.server == nil {
|
||||
+ return nil, ErrNodeStopped
|
||||
+ }
|
||||
+
|
||||
+ return rpc.DialInProc(n.inprocPublicHandler), nil
|
||||
+}
|
||||
+
|
||||
// RPCHandler returns the in-process RPC request handler.
|
||||
func (n *Node) RPCHandler() (*rpc.Server, error) {
|
||||
n.lock.RLock()
|
|
@ -109,6 +109,7 @@ func (b *StatusBackend) startNode(config *params.NodeConfig) (err error) {
|
|||
err = fmt.Errorf("node crashed on start: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = b.statusNode.Start(config)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
|
|
|
@ -28,15 +28,15 @@ var (
|
|||
|
||||
// PublicAPI defines a MailServer public API.
|
||||
type PublicAPI struct {
|
||||
provider ServiceProvider
|
||||
log log.Logger
|
||||
service *MailService
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
// NewPublicAPI returns a new PublicAPI.
|
||||
func NewPublicAPI(provider ServiceProvider) *PublicAPI {
|
||||
func NewPublicAPI(s *MailService) *PublicAPI {
|
||||
return &PublicAPI{
|
||||
provider: provider,
|
||||
log: log.New("package", "status-go/geth/mailservice.PublicAPI"),
|
||||
service: s,
|
||||
log: log.New("package", "status-go/geth/mailservice.PublicAPI"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,15 +75,7 @@ func (api *PublicAPI) RequestMessages(_ context.Context, r MessagesRequest) (boo
|
|||
|
||||
setMessagesRequestDefaults(&r)
|
||||
|
||||
shh, err := api.provider.WhisperService()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
node, err := api.provider.GethNode()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
shh := api.service.whisper
|
||||
|
||||
mailServerNode, err := discover.ParseNode(r.MailServerPeer)
|
||||
if err != nil {
|
||||
|
@ -95,7 +87,7 @@ func (api *PublicAPI) RequestMessages(_ context.Context, r MessagesRequest) (boo
|
|||
return false, fmt.Errorf("%v: %v", ErrInvalidSymKeyID, err)
|
||||
}
|
||||
|
||||
envelope, err := makeEnvelop(makePayload(r), symKey, node.Server().PrivateKey, shh.MinPow())
|
||||
envelope, err := makeEnvelop(makePayload(r), symKey, api.service.nodeID, shh.MinPow())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
|
@ -2,12 +2,13 @@ package mailservice
|
|||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -18,39 +19,44 @@ func TestRequestMessagesDefaults(t *testing.T) {
|
|||
require.InEpsilon(t, uint32(time.Now().UTC().Unix()), r.To, 1.0)
|
||||
}
|
||||
|
||||
func TestRequestMessagesFailures(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
provider := NewMockServiceProvider(ctrl)
|
||||
api := NewPublicAPI(provider)
|
||||
func TestRequestMessages(t *testing.T) {
|
||||
var err error
|
||||
|
||||
shh := whisper.New(nil)
|
||||
// Node is ephemeral (only in memory).
|
||||
nodeA, nodeErr := node.New(&node.Config{NoUSB: true})
|
||||
require.NoError(t, nodeErr)
|
||||
require.NoError(t, nodeA.Start())
|
||||
aNode, err := node.New(&node.Config{
|
||||
P2P: p2p.Config{
|
||||
MaxPeers: math.MaxInt32,
|
||||
NoDiscovery: true,
|
||||
},
|
||||
}) // in-memory node as no data dir
|
||||
require.NoError(t, err)
|
||||
err = aNode.Register(func(_ *node.ServiceContext) (node.Service, error) {
|
||||
return shh, nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = aNode.Start()
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
err := nodeA.Stop()
|
||||
err := aNode.Stop()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
service := New(shh)
|
||||
api := NewPublicAPI(service)
|
||||
|
||||
const (
|
||||
mailServerPeer = "enode://b7e65e1bedc2499ee6cbd806945af5e7df0e59e4070c96821570bd581473eade24a489f5ec95d060c0db118c879403ab88d827d3766978f28708989d35474f87@[::]:51920"
|
||||
)
|
||||
|
||||
var (
|
||||
result bool
|
||||
err error
|
||||
)
|
||||
var result bool
|
||||
|
||||
// invalid MailServer enode address
|
||||
provider.EXPECT().WhisperService().Return(nil, nil)
|
||||
provider.EXPECT().GethNode().Return(nil, nil)
|
||||
result, err = api.RequestMessages(context.TODO(), MessagesRequest{MailServerPeer: "invalid-address"})
|
||||
require.False(t, result)
|
||||
require.EqualError(t, err, "invalid mailServerPeer value: invalid URL scheme, want \"enode\"")
|
||||
|
||||
// non-existent symmetric key
|
||||
provider.EXPECT().WhisperService().Return(shh, nil)
|
||||
provider.EXPECT().GethNode().Return(nil, nil)
|
||||
result, err = api.RequestMessages(context.TODO(), MessagesRequest{
|
||||
MailServerPeer: mailServerPeer,
|
||||
})
|
||||
|
@ -60,19 +66,43 @@ func TestRequestMessagesFailures(t *testing.T) {
|
|||
// with a symmetric key
|
||||
symKeyID, symKeyErr := shh.AddSymKeyFromPassword("some-pass")
|
||||
require.NoError(t, symKeyErr)
|
||||
provider.EXPECT().WhisperService().Return(shh, nil)
|
||||
provider.EXPECT().GethNode().Return(nodeA, nil)
|
||||
result, err = api.RequestMessages(context.TODO(), MessagesRequest{
|
||||
MailServerPeer: mailServerPeer,
|
||||
SymKeyID: symKeyID,
|
||||
})
|
||||
require.Contains(t, err.Error(), "Could not find peer with ID")
|
||||
require.False(t, result)
|
||||
}
|
||||
|
||||
func TestRequestMessagesSuccess(t *testing.T) {
|
||||
// TODO(adam): next step would be to run a successful test, however,
|
||||
// it requires to set up emepheral nodes that can discover each other
|
||||
// without syncing blockchain. It requires a bit research how to do that.
|
||||
t.Skip()
|
||||
// with a peer acting as a mailserver
|
||||
// prepare a node first
|
||||
mailNode, err := node.New(&node.Config{
|
||||
P2P: p2p.Config{
|
||||
MaxPeers: math.MaxInt32,
|
||||
NoDiscovery: true,
|
||||
ListenAddr: ":0",
|
||||
},
|
||||
}) // in-memory node as no data dir
|
||||
require.NoError(t, err)
|
||||
err = mailNode.Register(func(_ *node.ServiceContext) (node.Service, error) {
|
||||
return whisper.New(nil), nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = mailNode.Start()
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
err := mailNode.Stop()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
// add mailPeer as a peer
|
||||
aNode.Server().AddPeer(mailNode.Server().Self())
|
||||
time.Sleep(time.Second) // wait for the peer to be added
|
||||
|
||||
// send a request
|
||||
result, err = api.RequestMessages(context.TODO(), MessagesRequest{
|
||||
MailServerPeer: mailNode.Server().Self().String(),
|
||||
SymKeyID: symKeyID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.True(t, result)
|
||||
}
|
|
@ -1,29 +1,26 @@
|
|||
package mailservice
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
)
|
||||
|
||||
// ServiceProvider provides node and required services.
|
||||
type ServiceProvider interface {
|
||||
GethNode() (*node.Node, error)
|
||||
WhisperService() (*whisper.Whisper, error)
|
||||
}
|
||||
|
||||
// MailService is a service that provides some additional Whisper API.
|
||||
type MailService struct {
|
||||
provider ServiceProvider
|
||||
whisper *whisper.Whisper
|
||||
nodeID *ecdsa.PrivateKey
|
||||
}
|
||||
|
||||
// Make sure that MailService implements node.Service interface.
|
||||
var _ node.Service = (*MailService)(nil)
|
||||
|
||||
// New returns a new MailService.
|
||||
func New(provider ServiceProvider) *MailService {
|
||||
return &MailService{provider}
|
||||
func New(w *whisper.Whisper) *MailService {
|
||||
return &MailService{whisper: w}
|
||||
}
|
||||
|
||||
// Protocols returns a new protocols list. In this case, there are none.
|
||||
|
@ -37,7 +34,7 @@ func (s *MailService) APIs() []rpc.API {
|
|||
{
|
||||
Namespace: "shh",
|
||||
Version: "1.0",
|
||||
Service: NewPublicAPI(s.provider),
|
||||
Service: NewPublicAPI(s),
|
||||
Public: true,
|
||||
},
|
||||
}
|
||||
|
@ -46,6 +43,7 @@ func (s *MailService) APIs() []rpc.API {
|
|||
// Start is run when a service is started.
|
||||
// It does nothing in this case but is required by `node.Service` interface.
|
||||
func (s *MailService) Start(server *p2p.Server) error {
|
||||
s.nodeID = server.PrivateKey
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/status-im/status-go/geth/mailservice (interfaces: ServiceProvider)
|
||||
|
||||
// Package mailservice is a generated GoMock package.
|
||||
package mailservice
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
node "github.com/ethereum/go-ethereum/node"
|
||||
whisperv6 "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockServiceProvider is a mock of ServiceProvider interface
|
||||
type MockServiceProvider struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockServiceProviderMockRecorder
|
||||
}
|
||||
|
||||
// MockServiceProviderMockRecorder is the mock recorder for MockServiceProvider
|
||||
type MockServiceProviderMockRecorder struct {
|
||||
mock *MockServiceProvider
|
||||
}
|
||||
|
||||
// NewMockServiceProvider creates a new mock instance
|
||||
func NewMockServiceProvider(ctrl *gomock.Controller) *MockServiceProvider {
|
||||
mock := &MockServiceProvider{ctrl: ctrl}
|
||||
mock.recorder = &MockServiceProviderMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockServiceProvider) EXPECT() *MockServiceProviderMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// GethNode mocks base method
|
||||
func (m *MockServiceProvider) GethNode() (*node.Node, error) {
|
||||
ret := m.ctrl.Call(m, "GethNode")
|
||||
ret0, _ := ret[0].(*node.Node)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GethNode indicates an expected call of Node
|
||||
func (mr *MockServiceProviderMockRecorder) GethNode() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GethNode", reflect.TypeOf((*MockServiceProvider)(nil).GethNode))
|
||||
}
|
||||
|
||||
// WhisperService mocks base method
|
||||
func (m *MockServiceProvider) WhisperService() (*whisperv6.Whisper, error) {
|
||||
ret := m.ctrl.Call(m, "WhisperService")
|
||||
ret0, _ := ret[0].(*whisperv6.Whisper)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// WhisperService indicates an expected call of WhisperService
|
||||
func (mr *MockServiceProviderMockRecorder) WhisperService() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WhisperService", reflect.TypeOf((*MockServiceProvider)(nil).WhisperService))
|
||||
}
|
|
@ -7,7 +7,6 @@ import (
|
|||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
|
@ -22,6 +21,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/p2p/nat"
|
||||
"github.com/ethereum/go-ethereum/whisper/mailserver"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
"github.com/status-im/status-go/geth/mailservice"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
shhmetrics "github.com/status-im/status-go/metrics/whisper"
|
||||
"github.com/status-im/status-go/shhext"
|
||||
|
@ -105,12 +105,12 @@ func defaultEmbeddedNodeConfig(config *params.NodeConfig) *node.Config {
|
|||
},
|
||||
IPCPath: makeIPCPath(config),
|
||||
HTTPCors: []string{"*"},
|
||||
HTTPModules: strings.Split(config.APIModules, ","),
|
||||
HTTPModules: config.FormatAPIModules(),
|
||||
HTTPVirtualHosts: []string{"localhost"},
|
||||
WSHost: makeWSHost(config),
|
||||
WSPort: config.WSPort,
|
||||
WSOrigins: []string{"*"},
|
||||
WSModules: strings.Split(config.APIModules, ","),
|
||||
WSModules: config.FormatAPIModules(),
|
||||
}
|
||||
|
||||
if config.RPCEnabled {
|
||||
|
@ -157,28 +157,26 @@ func activateEthService(stack *node.Node, config *params.NodeConfig) error {
|
|||
}
|
||||
|
||||
// activateShhService configures Whisper and adds it to the given node.
|
||||
func activateShhService(stack *node.Node, config *params.NodeConfig) error {
|
||||
|
||||
func activateShhService(stack *node.Node, config *params.NodeConfig) (err error) {
|
||||
if !config.WhisperConfig.Enabled {
|
||||
logger.Info("SHH protocol is disabled")
|
||||
return nil
|
||||
}
|
||||
var whisperService *whisper.Whisper
|
||||
if err := stack.Register(func(*node.ServiceContext) (node.Service, error) {
|
||||
|
||||
err = stack.Register(func(*node.ServiceContext) (node.Service, error) {
|
||||
whisperServiceConfig := &whisper.Config{
|
||||
MaxMessageSize: whisper.DefaultMaxMessageSize,
|
||||
MinimumAcceptedPOW: 0.001,
|
||||
}
|
||||
whisperService = whisper.New(whisperServiceConfig)
|
||||
whisperService := whisper.New(whisperServiceConfig)
|
||||
|
||||
whisperConfig := config.WhisperConfig
|
||||
// enable metrics
|
||||
whisperService.RegisterEnvelopeTracer(&shhmetrics.EnvelopeTracer{})
|
||||
|
||||
// enable mail service
|
||||
if whisperConfig.EnableMailServer {
|
||||
if whisperConfig.Password == "" {
|
||||
if err := whisperConfig.ReadPasswordFile(); err != nil {
|
||||
if config.WhisperConfig.EnableMailServer {
|
||||
if config.WhisperConfig.Password == "" {
|
||||
if err := config.WhisperConfig.ReadPasswordFile(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
@ -187,10 +185,15 @@ func activateShhService(stack *node.Node, config *params.NodeConfig) error {
|
|||
|
||||
var mailServer mailserver.WMailServer
|
||||
whisperService.RegisterServer(&mailServer)
|
||||
mailServer.Init(whisperService, whisperConfig.DataDir, whisperConfig.Password, whisperConfig.MinimumPoW)
|
||||
mailServer.Init(
|
||||
whisperService,
|
||||
config.WhisperConfig.DataDir,
|
||||
config.WhisperConfig.Password,
|
||||
config.WhisperConfig.MinimumPoW,
|
||||
)
|
||||
}
|
||||
|
||||
if whisperConfig.LightClient {
|
||||
if config.WhisperConfig.LightClient {
|
||||
emptyBloomFilter := make([]byte, 64)
|
||||
if err := whisperService.SetBloomFilter(emptyBloomFilter); err != nil {
|
||||
return nil, err
|
||||
|
@ -198,14 +201,33 @@ func activateShhService(stack *node.Node, config *params.NodeConfig) error {
|
|||
}
|
||||
|
||||
return whisperService, nil
|
||||
}); err != nil {
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(dshulyak) add a config option to enable it by default, but disable if app is started from statusd
|
||||
return stack.Register(func(*node.ServiceContext) (node.Service, error) {
|
||||
svc := shhext.New(whisperService, shhext.SendEnvelopeSentSignal)
|
||||
err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||
var whisper *whisper.Whisper
|
||||
if err := ctx.Service(&whisper); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svc := shhext.New(whisper, shhext.SendEnvelopeSentSignal)
|
||||
return svc, nil
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||
var whisper *whisper.Whisper
|
||||
if err := ctx.Service(&whisper); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mailservice.New(whisper), nil
|
||||
})
|
||||
}
|
||||
|
||||
// makeIPCPath returns IPC-RPC filename
|
||||
|
|
|
@ -19,7 +19,6 @@ import (
|
|||
"github.com/syndtr/goleveldb/leveldb"
|
||||
|
||||
"github.com/status-im/status-go/geth/db"
|
||||
"github.com/status-im/status-go/geth/mailservice"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
"github.com/status-im/status-go/geth/peers"
|
||||
"github.com/status-im/status-go/geth/rpc"
|
||||
|
@ -45,16 +44,17 @@ type EthNodeError error
|
|||
// StatusNode abstracts contained geth node and provides helper methods to
|
||||
// interact with it.
|
||||
type StatusNode struct {
|
||||
mu sync.RWMutex
|
||||
config *params.NodeConfig // Status node configuration
|
||||
gethNode *node.Node // reference to Geth P2P stack/node
|
||||
mu sync.RWMutex
|
||||
|
||||
config *params.NodeConfig // Status node configuration
|
||||
gethNode *node.Node // reference to Geth P2P stack/node
|
||||
rpcClient *rpc.Client // reference to public RPC client
|
||||
|
||||
register *peers.Register
|
||||
peerPool *peers.PeerPool
|
||||
db *leveldb.DB
|
||||
db *leveldb.DB // used as a cache for PeerPool
|
||||
|
||||
rpcClient *rpc.Client // reference to RPC client
|
||||
log log.Logger
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
// New makes new instance of StatusNode.
|
||||
|
@ -65,18 +65,19 @@ func New() *StatusNode {
|
|||
}
|
||||
|
||||
// Start starts current StatusNode, will fail if it's already started.
|
||||
func (n *StatusNode) Start(config *params.NodeConfig) error {
|
||||
func (n *StatusNode) Start(config *params.NodeConfig, services ...node.ServiceConstructor) error {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
return n.start(config)
|
||||
}
|
||||
|
||||
// start starts current StatusNode, will fail if it's already started.
|
||||
func (n *StatusNode) start(config *params.NodeConfig) error {
|
||||
if err := n.isAvailable(); err == nil {
|
||||
return ErrNodeExists
|
||||
}
|
||||
|
||||
return n.start(config, services)
|
||||
}
|
||||
|
||||
// start starts current StatusNode, will fail if it's already started.
|
||||
func (n *StatusNode) start(config *params.NodeConfig, services []node.ServiceConstructor) error {
|
||||
ethNode, err := MakeNode(config)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -84,11 +85,10 @@ func (n *StatusNode) start(config *params.NodeConfig) error {
|
|||
n.gethNode = ethNode
|
||||
n.config = config
|
||||
|
||||
// activate MailService required for Offline Inboxing
|
||||
if err := ethNode.Register(func(_ *node.ServiceContext) (node.Service, error) {
|
||||
return mailservice.New(n), nil
|
||||
}); err != nil {
|
||||
return err
|
||||
for _, service := range services {
|
||||
if err := ethNode.Register(service); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// start underlying node
|
||||
|
@ -96,7 +96,7 @@ func (n *StatusNode) start(config *params.NodeConfig) error {
|
|||
return EthNodeError(err)
|
||||
}
|
||||
// init RPC client for this node
|
||||
localRPCClient, err := n.gethNode.Attach()
|
||||
localRPCClient, err := n.gethNode.AttachPublic()
|
||||
if err == nil {
|
||||
n.rpcClient, err = rpc.NewClient(localRPCClient, n.config.UpstreamConfig)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
package node_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
gethnode "github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
|
||||
"github.com/status-im/status-go/geth/node"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
. "github.com/status-im/status-go/t/utils"
|
||||
)
|
||||
|
||||
type TestServiceAPI struct{}
|
||||
|
||||
func (api *TestServiceAPI) SomeMethod(_ context.Context) (string, error) {
|
||||
return "some method result", nil
|
||||
}
|
||||
|
||||
type testService struct{}
|
||||
|
||||
func (s *testService) Protocols() []p2p.Protocol {
|
||||
return []p2p.Protocol{}
|
||||
}
|
||||
|
||||
func (s *testService) APIs() []rpc.API {
|
||||
return []rpc.API{
|
||||
{
|
||||
Namespace: "pri",
|
||||
Version: "1.0",
|
||||
Service: &TestServiceAPI{},
|
||||
Public: false,
|
||||
},
|
||||
{
|
||||
Namespace: "pub",
|
||||
Version: "1.0",
|
||||
Service: &TestServiceAPI{},
|
||||
Public: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *testService) Start(server *p2p.Server) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *testService) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func createStatusNode(config *params.NodeConfig) (*node.StatusNode, error) {
|
||||
services := []gethnode.ServiceConstructor{
|
||||
func(_ *gethnode.ServiceContext) (gethnode.Service, error) {
|
||||
return &testService{}, nil
|
||||
},
|
||||
}
|
||||
statusNode := node.New()
|
||||
return statusNode, statusNode.Start(config, services...)
|
||||
}
|
||||
|
||||
func TestNodeRPCClientCallOnlyPublicAPIs(t *testing.T) {
|
||||
var err error
|
||||
|
||||
config, err := MakeTestNodeConfig(GetNetworkID())
|
||||
require.NoError(t, err)
|
||||
config.APIModules = "" // no whitelisted API modules; use only public APIs
|
||||
|
||||
statusNode, err := createStatusNode(config)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
err := statusNode.Stop()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
client := statusNode.RPCClient()
|
||||
require.NotNil(t, client)
|
||||
|
||||
var result string
|
||||
|
||||
// call public API
|
||||
err = client.Call(&result, "pub_someMethod")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "some method result", result)
|
||||
|
||||
// call private API
|
||||
err = client.Call(&result, "pri_someMethod")
|
||||
require.EqualError(t, err, "The method pri_someMethod does not exist/is not available")
|
||||
}
|
||||
|
||||
func TestNodeRPCClientCallWhitelistedPrivateService(t *testing.T) {
|
||||
var err error
|
||||
|
||||
config, err := MakeTestNodeConfig(GetNetworkID())
|
||||
require.NoError(t, err)
|
||||
config.APIModules = "pri"
|
||||
|
||||
statusNode, err := createStatusNode(config)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
err := statusNode.Stop()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
client := statusNode.RPCClient()
|
||||
require.NotNil(t, client)
|
||||
|
||||
// call private API
|
||||
var result string
|
||||
err = client.Call(&result, "pri_someMethod")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "some method result", result)
|
||||
}
|
|
@ -611,3 +611,12 @@ func (c *NodeConfig) String() string {
|
|||
data, _ := json.MarshalIndent(c, "", " ")
|
||||
return string(data)
|
||||
}
|
||||
|
||||
// FormatAPIModules returns a slice of APIModules.
|
||||
func (c *NodeConfig) FormatAPIModules() []string {
|
||||
if len(c.APIModules) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return strings.Split(c.APIModules, ",")
|
||||
}
|
||||
|
|
|
@ -30,15 +30,17 @@ const (
|
|||
ListenAddr = ":0"
|
||||
|
||||
// APIModules is a list of modules to expose via any type of RPC (HTTP, IPC, in-proc)
|
||||
APIModules = "eth,net,web3,shh,personal"
|
||||
APIModules = "eth,net,web3,shh,shhext"
|
||||
|
||||
// WSHost is a host interface for the websocket RPC server
|
||||
WSHost = "localhost"
|
||||
|
||||
// SendTransactionMethodName defines the name for a giving transaction.
|
||||
SendTransactionMethodName = "eth_sendTransaction"
|
||||
|
||||
// AccountsMethodName defines the name for listing the currently signed accounts.
|
||||
AccountsMethodName = "eth_accounts"
|
||||
|
||||
// PersonalSignMethodName defines the name for `personal.sign` API.
|
||||
PersonalSignMethodName = "personal_sign"
|
||||
|
||||
|
|
|
@ -4,12 +4,12 @@ import (
|
|||
"github.com/ethereum/go-ethereum/log"
|
||||
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
"github.com/status-im/status-go/geth/api"
|
||||
"github.com/status-im/status-go/sign"
|
||||
|
||||
"github.com/status-im/status-go/geth/api"
|
||||
"github.com/status-im/status-go/geth/node"
|
||||
"github.com/status-im/status-go/geth/signal"
|
||||
"github.com/status-im/status-go/geth/transactions"
|
||||
"github.com/status-im/status-go/sign"
|
||||
. "github.com/status-im/status-go/t/utils" //nolint: golint
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
|
||||
"github.com/status-im/status-go/geth/node"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
e2e "github.com/status-im/status-go/t/e2e"
|
||||
"github.com/status-im/status-go/t/e2e"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
|
|
|
@ -51,8 +51,9 @@ type Node struct {
|
|||
serviceFuncs []ServiceConstructor // Service constructors (in dependency order)
|
||||
services map[reflect.Type]Service // Currently running services
|
||||
|
||||
rpcAPIs []rpc.API // List of APIs currently provided by the node
|
||||
inprocHandler *rpc.Server // In-process RPC request handler to process the API requests
|
||||
rpcAPIs []rpc.API // List of APIs currently provided by the node
|
||||
inprocHandler *rpc.Server // In-process RPC request handler to process the API requests
|
||||
inprocPublicHandler *rpc.Server // In-process RPC request handler to process the public API requests
|
||||
|
||||
ipcEndpoint string // IPC endpoint to listen at (empty = IPC disabled)
|
||||
ipcListener net.Listener // IPC RPC listener socket to serve API requests
|
||||
|
@ -259,18 +260,25 @@ func (n *Node) startRPC(services map[reflect.Type]Service) error {
|
|||
if err := n.startInProc(apis); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := n.startPublicInProc(apis, n.config.HTTPModules); err != nil {
|
||||
n.stopInProc()
|
||||
return err
|
||||
}
|
||||
if err := n.startIPC(apis); err != nil {
|
||||
n.stopPublicInProc()
|
||||
n.stopInProc()
|
||||
return err
|
||||
}
|
||||
if err := n.startHTTP(n.httpEndpoint, apis, n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPVirtualHosts); err != nil {
|
||||
n.stopIPC()
|
||||
n.stopPublicInProc()
|
||||
n.stopInProc()
|
||||
return err
|
||||
}
|
||||
if err := n.startWS(n.wsEndpoint, apis, n.config.WSModules, n.config.WSOrigins, n.config.WSExposeAll); err != nil {
|
||||
n.stopHTTP()
|
||||
n.stopIPC()
|
||||
n.stopPublicInProc()
|
||||
n.stopInProc()
|
||||
return err
|
||||
}
|
||||
|
@ -301,6 +309,36 @@ func (n *Node) stopInProc() {
|
|||
}
|
||||
}
|
||||
|
||||
// startPublicInProc initializes an in-process RPC endpoint for public APIs.
|
||||
func (n *Node) startPublicInProc(apis []rpc.API, modules []string) error {
|
||||
// Generate the whitelist based on the allowed modules
|
||||
whitelist := make(map[string]bool)
|
||||
for _, module := range modules {
|
||||
whitelist[module] = true
|
||||
}
|
||||
|
||||
// Register all the public APIs exposed by the services
|
||||
handler := rpc.NewServer()
|
||||
for _, api := range apis {
|
||||
if whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) {
|
||||
if err := handler.RegisterName(api.Namespace, api.Service); err != nil {
|
||||
return err
|
||||
}
|
||||
n.log.Debug("InProc public registered", "service", api.Service, "namespace", api.Namespace)
|
||||
}
|
||||
}
|
||||
n.inprocPublicHandler = handler
|
||||
return nil
|
||||
}
|
||||
|
||||
// stopPublicInProc terminates the in-process RPC endpoint for public APIs.
|
||||
func (n *Node) stopPublicInProc() {
|
||||
if n.inprocPublicHandler != nil {
|
||||
n.inprocPublicHandler.Stop()
|
||||
n.inprocPublicHandler = nil
|
||||
}
|
||||
}
|
||||
|
||||
// startIPC initializes and starts the IPC RPC endpoint.
|
||||
func (n *Node) startIPC(apis []rpc.API) error {
|
||||
// Short circuit if the IPC endpoint isn't being exposed
|
||||
|
@ -562,6 +600,18 @@ func (n *Node) Attach() (*rpc.Client, error) {
|
|||
return rpc.DialInProc(n.inprocHandler), nil
|
||||
}
|
||||
|
||||
// AttachPublic creates an RPC client attached to an in-process Public API handler.
|
||||
func (n *Node) AttachPublic() (*rpc.Client, error) {
|
||||
n.lock.RLock()
|
||||
defer n.lock.RUnlock()
|
||||
|
||||
if n.server == nil {
|
||||
return nil, ErrNodeStopped
|
||||
}
|
||||
|
||||
return rpc.DialInProc(n.inprocPublicHandler), nil
|
||||
}
|
||||
|
||||
// RPCHandler returns the in-process RPC request handler.
|
||||
func (n *Node) RPCHandler() (*rpc.Server, error) {
|
||||
n.lock.RLock()
|
||||
|
|
Loading…
Reference in New Issue