Add CallPrivateRPC binding (#825)

This PR provides CallPrivateRPC binding, which can call both public and private bindings but should not be used in web3.js provider implementations.
This commit is contained in:
Adam Babik 2018-04-16 10:01:37 +02:00 committed by GitHub
parent 0b123ed407
commit ef160e8720
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 115 additions and 12 deletions

View File

@ -103,11 +103,16 @@ func (api *StatusAPI) ResetChainDataAsync() <-chan error {
return runAsync(api.ResetChainData)
}
// CallRPC executes RPC request on node's in-proc RPC server
// CallRPC executes public RPC requests on node's in-proc RPC server.
func (api *StatusAPI) CallRPC(inputJSON string) string {
return api.b.CallRPC(inputJSON)
}
// CallPrivateRPC executes public and private RPC requests on node's in-proc RPC server.
func (api *StatusAPI) CallPrivateRPC(inputJSON string) string {
return api.b.CallPrivateRPC(inputJSON)
}
// CreateAccount creates an internal geth account
// BIP44-compatible keys are generated: CKD#1 is stored as account key, CKD#2 stored as sub-account root
// Public key of CKD#1 is returned, with CKD#2 securely encoded into account key file (to be used for

View File

@ -194,12 +194,18 @@ func (b *StatusBackend) ResetChainData() error {
return b.startNode(&newcfg)
}
// CallRPC executes RPC request on node's in-proc RPC server
// CallRPC executes public RPC requests on node's in-proc RPC server.
func (b *StatusBackend) CallRPC(inputJSON string) string {
client := b.statusNode.RPCClient()
return client.CallRaw(inputJSON)
}
// CallPrivateRPC executes public and private RPC requests on node's in-proc RPC server.
func (b *StatusBackend) CallPrivateRPC(inputJSON string) string {
client := b.statusNode.RPCPrivateClient()
return client.CallRaw(inputJSON)
}
// SendTransaction creates a new transaction and waits until it's complete.
func (b *StatusBackend) SendTransaction(ctx context.Context, args transactions.SendTxArgs) (hash gethcommon.Hash, err error) {
return b.transactor.SendTransaction(ctx, args)

View File

@ -46,9 +46,10 @@ type EthNodeError error
type StatusNode struct {
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
config *params.NodeConfig // Status node configuration
gethNode *node.Node // reference to Geth P2P stack/node
rpcClient *rpc.Client // reference to public RPC client
rpcPrivateClient *rpc.Client // reference to private RPC client (can call private APIs)
register *peers.Register
peerPool *peers.PeerPool
@ -95,21 +96,42 @@ func (n *StatusNode) start(config *params.NodeConfig, services []node.ServiceCon
if err := ethNode.Start(); err != nil {
return EthNodeError(err)
}
// init RPC client for this node
localRPCClient, err := n.gethNode.AttachPublic()
if err == nil {
n.rpcClient, err = rpc.NewClient(localRPCClient, n.config.UpstreamConfig)
}
if err != nil {
if err := n.setupRPCClient(); err != nil {
n.log.Error("Failed to create an RPC client", "error", err)
return RPCClientError(err)
}
// start peer pool only if Discovery V5 is enabled
if ethNode.Server().DiscV5 != nil {
return n.startPeerPool()
}
return nil
}
func (n *StatusNode) setupRPCClient() (err error) {
// setup public RPC client
gethNodeClient, err := n.gethNode.AttachPublic()
if err != nil {
return
}
n.rpcClient, err = rpc.NewClient(gethNodeClient, n.config.UpstreamConfig)
if err != nil {
return
}
// setup private RPC client
gethNodePrivateClient, err := n.gethNode.Attach()
if err != nil {
return
}
n.rpcPrivateClient, err = rpc.NewClient(gethNodePrivateClient, n.config.UpstreamConfig)
return
}
func (n *StatusNode) startPeerPool() error {
statusDB, err := db.Create(filepath.Join(n.config.DataDir, params.StatusDatabase))
if err != nil {
@ -152,6 +174,7 @@ func (n *StatusNode) stop() error {
n.gethNode = nil
n.config = nil
n.rpcClient = nil
n.rpcPrivateClient = nil
return nil
}
@ -377,6 +400,14 @@ func (n *StatusNode) RPCClient() *rpc.Client {
return n.rpcClient
}
// RPCPrivateClient exposes reference to RPC client connected to the running node
// that can call both public and private APIs.
func (n *StatusNode) RPCPrivateClient() *rpc.Client {
n.mu.Lock()
defer n.mu.Unlock()
return n.rpcPrivateClient
}
// isAvailable check if we have a node running and make sure is fully started
func (n *StatusNode) isAvailable() error {
if n.gethNode == nil || n.gethNode.Server() == nil {

View File

@ -86,7 +86,7 @@ func TestNodeRPCClientCallOnlyPublicAPIs(t *testing.T) {
require.NoError(t, err)
require.Equal(t, "some method result", result)
// call private API
// call private API with public RPC client
err = client.Call(&result, "pri_someMethod")
require.EqualError(t, err, "The method pri_someMethod does not exist/is not available")
}
@ -114,3 +114,26 @@ func TestNodeRPCClientCallWhitelistedPrivateService(t *testing.T) {
require.NoError(t, err)
require.Equal(t, "some method result", result)
}
func TestNodeRPCPrivateClientCallPrivateService(t *testing.T) {
var err error
config, err := MakeTestNodeConfig(GetNetworkID())
require.NoError(t, err)
statusNode, err := createStatusNode(config)
require.NoError(t, err)
defer func() {
err := statusNode.Stop()
require.NoError(t, err)
}()
client := statusNode.RPCPrivateClient()
require.NotNil(t, client)
// call private API with private RPC client
var result string
err = client.Call(&result, "pri_someMethod")
require.NoError(t, err)
require.Equal(t, "some method result", result)
}

View File

@ -101,13 +101,20 @@ func ResetChainData() *C.char {
return makeJSONResponse(nil)
}
//CallRPC calls status node via rpc
//CallRPC calls public APIs via RPC
//export CallRPC
func CallRPC(inputJSON *C.char) *C.char {
outputJSON := statusAPI.CallRPC(C.GoString(inputJSON))
return C.CString(outputJSON)
}
//CallPrivateRPC calls both public and private APIs via RPC
//export CallPrivateRPC
func CallPrivateRPC(inputJSON *C.char) *C.char {
outputJSON := statusAPI.CallPrivateRPC(C.GoString(inputJSON))
return C.CString(outputJSON)
}
//CreateAccount is equivalent to creating an account from the command line,
// just modified to handle the function arg passing
//export CreateAccount

View File

@ -96,6 +96,14 @@ func testExportedAPI(t *testing.T, done chan struct{}) {
"call RPC on in-proc handler",
testCallRPC,
},
{
"call private API using RPC",
testCallRPCWithPrivateAPI,
},
{
"call private API using private RPC client",
testCallPrivateRPCWithPrivateAPI,
},
{
"create main and child accounts",
testCreateChildAccount,
@ -381,6 +389,29 @@ func testCallRPC(t *testing.T) bool {
return true
}
func testCallRPCWithPrivateAPI(t *testing.T) bool {
expected := `{"jsonrpc":"2.0","id":64,"error":{"code":-32601,"message":"The method admin_nodeInfo does not exist/is not available"}}`
rawResponse := CallRPC(C.CString(`{"jsonrpc":"2.0","method":"admin_nodeInfo","params":[],"id":64}`))
received := C.GoString(rawResponse)
if expected != received {
t.Errorf("unexpected response: expected: %v, got: %v", expected, received)
return false
}
return true
}
func testCallPrivateRPCWithPrivateAPI(t *testing.T) bool {
rawResponse := CallPrivateRPC(C.CString(`{"jsonrpc":"2.0","method":"admin_nodeInfo","params":[],"id":64}`))
received := C.GoString(rawResponse)
if strings.Contains(received, "error") {
t.Errorf("unexpected response containing error: %v", received)
return false
}
return true
}
func testCreateChildAccount(t *testing.T) bool { //nolint: gocyclo
// to make sure that we start with empty account (which might get populated during previous tests)
if err := statusAPI.Logout(); err != nil {