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:
Adam Babik 2018-04-12 18:17:10 +02:00 committed by GitHub
parent a7a2e01b4a
commit 0d652c3851
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 411 additions and 156 deletions

View File

@ -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

View File

@ -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()

View File

@ -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) {

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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))
}

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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, ",")
}

View File

@ -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"

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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()