Fixed eth_sendTransaction routing to the local node (#351)
* Fixed eth_sendTransaction routing to the local node * Add local RPC handlers for eth_accounts and eth_sendTransaction
This commit is contained in:
parent
750612f2bc
commit
fc8f59e121
|
@ -5,7 +5,6 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
gethcommon "github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/les"
|
|
||||||
"github.com/status-im/status-go/geth/common"
|
"github.com/status-im/status-go/geth/common"
|
||||||
"github.com/status-im/status-go/geth/jail"
|
"github.com/status-im/status-go/geth/jail"
|
||||||
"github.com/status-im/status-go/geth/log"
|
"github.com/status-im/status-go/geth/log"
|
||||||
|
@ -224,19 +223,9 @@ func (m *StatusBackend) DiscardTransactions(ids []common.QueuedTxID) map[common.
|
||||||
|
|
||||||
// registerHandlers attaches Status callback handlers to running node
|
// registerHandlers attaches Status callback handlers to running node
|
||||||
func (m *StatusBackend) registerHandlers() error {
|
func (m *StatusBackend) registerHandlers() error {
|
||||||
runningNode, err := m.nodeManager.Node()
|
rpcClient := m.NodeManager().RPCClient()
|
||||||
if err != nil {
|
rpcClient.RegisterHandler("eth_accounts", m.accountManager.AccountsRPCHandler())
|
||||||
return err
|
rpcClient.RegisterHandler("eth_sendTransaction", m.txQueueManager.SendTransactionRPCHandler)
|
||||||
}
|
|
||||||
|
|
||||||
var lightEthereum *les.LightEthereum
|
|
||||||
if err := runningNode.Service(&lightEthereum); err != nil {
|
|
||||||
log.Error("Cannot get light ethereum service", "error", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
lightEthereum.StatusBackend.SetAccountsFilterHandler(m.accountManager.AccountsListRequestHandler())
|
|
||||||
log.Info("Registered handler", "fn", "AccountsFilterHandler")
|
|
||||||
|
|
||||||
m.txQueueManager.SetTransactionQueueHandler(m.txQueueManager.TransactionQueueHandler())
|
m.txQueueManager.SetTransactionQueueHandler(m.txQueueManager.TransactionQueueHandler())
|
||||||
log.Info("Registered handler", "fn", "TransactionQueueHandler")
|
log.Info("Registered handler", "fn", "TransactionQueueHandler")
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/les"
|
|
||||||
"github.com/status-im/status-go/geth/common"
|
"github.com/status-im/status-go/geth/common"
|
||||||
"github.com/status-im/status-go/geth/node"
|
"github.com/status-im/status-go/geth/node"
|
||||||
"github.com/status-im/status-go/geth/params"
|
"github.com/status-im/status-go/geth/params"
|
||||||
|
@ -22,14 +21,8 @@ func (s *BackendTestSuite) TestAccountsList() {
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
require.NotNil(runningNode)
|
require.NotNil(runningNode)
|
||||||
|
|
||||||
var lesService *les.LightEthereum
|
accounts, err := s.backend.AccountManager().Accounts()
|
||||||
require.NoError(runningNode.Service(&lesService))
|
require.NoError(err)
|
||||||
require.NotNil(lesService)
|
|
||||||
|
|
||||||
accounts := lesService.StatusBackend.AccountManager().Accounts()
|
|
||||||
for _, acc := range accounts {
|
|
||||||
fmt.Println(acc.Hex())
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure that we start with empty accounts list (nobody has logged in yet)
|
// make sure that we start with empty accounts list (nobody has logged in yet)
|
||||||
require.Zero(len(accounts), "accounts returned, while there should be none (we haven't logged in yet)")
|
require.Zero(len(accounts), "accounts returned, while there should be none (we haven't logged in yet)")
|
||||||
|
@ -39,7 +32,8 @@ func (s *BackendTestSuite) TestAccountsList() {
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
|
|
||||||
// ensure that there is still no accounts returned
|
// ensure that there is still no accounts returned
|
||||||
accounts = lesService.StatusBackend.AccountManager().Accounts()
|
accounts, err = s.backend.AccountManager().Accounts()
|
||||||
|
require.NoError(err)
|
||||||
require.Zero(len(accounts), "accounts returned, while there should be none (we haven't logged in yet)")
|
require.Zero(len(accounts), "accounts returned, while there should be none (we haven't logged in yet)")
|
||||||
|
|
||||||
// select account (sub-accounts will be created for this key)
|
// select account (sub-accounts will be created for this key)
|
||||||
|
@ -47,7 +41,8 @@ func (s *BackendTestSuite) TestAccountsList() {
|
||||||
require.NoError(err, "account selection failed")
|
require.NoError(err, "account selection failed")
|
||||||
|
|
||||||
// at this point main account should show up
|
// at this point main account should show up
|
||||||
accounts = lesService.StatusBackend.AccountManager().Accounts()
|
accounts, err = s.backend.AccountManager().Accounts()
|
||||||
|
require.NoError(err)
|
||||||
require.Equal(1, len(accounts), "exactly single account is expected (main account)")
|
require.Equal(1, len(accounts), "exactly single account is expected (main account)")
|
||||||
require.Equal(string(accounts[0].Hex()), "0x"+address,
|
require.Equal(string(accounts[0].Hex()), "0x"+address,
|
||||||
fmt.Sprintf("main account is not retured as the first key: got %s, expected %s", accounts[0].Hex(), "0x"+address))
|
fmt.Sprintf("main account is not retured as the first key: got %s, expected %s", accounts[0].Hex(), "0x"+address))
|
||||||
|
@ -57,7 +52,8 @@ func (s *BackendTestSuite) TestAccountsList() {
|
||||||
require.NoError(err, "cannot create sub-account")
|
require.NoError(err, "cannot create sub-account")
|
||||||
|
|
||||||
// now we expect to see both main account and sub-account 1
|
// now we expect to see both main account and sub-account 1
|
||||||
accounts = lesService.StatusBackend.AccountManager().Accounts()
|
accounts, err = s.backend.AccountManager().Accounts()
|
||||||
|
require.NoError(err)
|
||||||
require.Equal(2, len(accounts), "exactly 2 accounts are expected (main + sub-account 1)")
|
require.Equal(2, len(accounts), "exactly 2 accounts are expected (main + sub-account 1)")
|
||||||
require.Equal(string(accounts[0].Hex()), "0x"+address, "main account is not retured as the first key")
|
require.Equal(string(accounts[0].Hex()), "0x"+address, "main account is not retured as the first key")
|
||||||
require.Equal(string(accounts[1].Hex()), "0x"+subAccount1, "subAcount1 not returned")
|
require.Equal(string(accounts[1].Hex()), "0x"+subAccount1, "subAcount1 not returned")
|
||||||
|
@ -68,7 +64,8 @@ func (s *BackendTestSuite) TestAccountsList() {
|
||||||
require.False(subAccount1 == subAccount2 || subPubKey1 == subPubKey2, "sub-account index auto-increament failed")
|
require.False(subAccount1 == subAccount2 || subPubKey1 == subPubKey2, "sub-account index auto-increament failed")
|
||||||
|
|
||||||
// finally, all 3 accounts should show up (main account, sub-accounts 1 and 2)
|
// finally, all 3 accounts should show up (main account, sub-accounts 1 and 2)
|
||||||
accounts = lesService.StatusBackend.AccountManager().Accounts()
|
accounts, err = s.backend.AccountManager().Accounts()
|
||||||
|
require.NoError(err)
|
||||||
require.Equal(3, len(accounts), "unexpected number of accounts")
|
require.Equal(3, len(accounts), "unexpected number of accounts")
|
||||||
require.Equal(string(accounts[0].Hex()), "0x"+address, "main account is not retured as the first key")
|
require.Equal(string(accounts[0].Hex()), "0x"+address, "main account is not retured as the first key")
|
||||||
|
|
||||||
|
|
|
@ -388,7 +388,6 @@ func (s *BackendTestSuite) TestJailWhisper() {
|
||||||
throw 'message not sent: ' + JSON.stringify(message);
|
throw 'message not sent: ' + JSON.stringify(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var filterName = '` + whisperMessage1 + `';
|
var filterName = '` + whisperMessage1 + `';
|
||||||
var filterId = filter.filterId;
|
var filterId = filter.filterId;
|
||||||
if (!filterId) {
|
if (!filterId) {
|
||||||
|
@ -576,11 +575,13 @@ func (s *BackendTestSuite) TestJailWhisper() {
|
||||||
|
|
||||||
jailInstance.Parse(testCaseKey, `
|
jailInstance.Parse(testCaseKey, `
|
||||||
var shh = web3.shh;
|
var shh = web3.shh;
|
||||||
|
// topic must be 4-byte long
|
||||||
var makeTopic = function () {
|
var makeTopic = function () {
|
||||||
var min = 1;
|
var topic = '0x';
|
||||||
var max = Math.pow(16, 8);
|
for (var i = 0; i < 8; i++) {
|
||||||
var randInt = Math.floor(Math.random() * (max - min + 1)) + min;
|
topic += Math.floor(Math.random() * 16).toString(16);
|
||||||
return web3.toHex(randInt);
|
}
|
||||||
|
return topic;
|
||||||
};
|
};
|
||||||
`)
|
`)
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package api_test
|
package api_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
gethcommon "github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/les"
|
"github.com/ethereum/go-ethereum/les"
|
||||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv5"
|
whisper "github.com/ethereum/go-ethereum/whisper/whisperv5"
|
||||||
"github.com/status-im/status-go/geth/api"
|
"github.com/status-im/status-go/geth/api"
|
||||||
|
@ -256,6 +258,119 @@ func (s *BackendTestSuite) TestCallRPC() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *BackendTestSuite) TestCallRPCSendTransaction() {
|
||||||
|
nodeConfig, err := MakeTestNodeConfig(params.RopstenNetworkID)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
nodeStarted, err := s.backend.StartNode(nodeConfig)
|
||||||
|
s.NoError(err)
|
||||||
|
defer s.backend.StopNode()
|
||||||
|
|
||||||
|
<-nodeStarted
|
||||||
|
|
||||||
|
// Allow to sync the blockchain.
|
||||||
|
time.Sleep(TestConfig.Node.SyncSeconds * time.Second)
|
||||||
|
|
||||||
|
err = s.backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
transactionCompleted := make(chan struct{})
|
||||||
|
|
||||||
|
var txHash gethcommon.Hash
|
||||||
|
node.SetDefaultNodeNotificationHandler(func(rawSignal string) {
|
||||||
|
var signal node.SignalEnvelope
|
||||||
|
err := json.Unmarshal([]byte(rawSignal), &signal)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
if signal.Type == node.EventTransactionQueued {
|
||||||
|
event := signal.Event.(map[string]interface{})
|
||||||
|
txID := event["id"].(string)
|
||||||
|
txHash, err = s.backend.CompleteTransaction(common.QueuedTxID(txID), TestConfig.Account1.Password)
|
||||||
|
s.NoError(err, "cannot complete queued transaction %s", txID)
|
||||||
|
|
||||||
|
close(transactionCompleted)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
result := s.backend.CallRPC(`{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"method": "eth_sendTransaction",
|
||||||
|
"params": [{
|
||||||
|
"from": "` + TestConfig.Account1.Address + `",
|
||||||
|
"to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
|
||||||
|
"value": "0x9184e72a"
|
||||||
|
}]
|
||||||
|
}`)
|
||||||
|
s.NotContains(result, "error")
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-transactionCompleted:
|
||||||
|
case <-time.After(time.Minute):
|
||||||
|
s.FailNow("sending transaction timed out")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Equal(`{"jsonrpc":"2.0","id":1,"result":"`+txHash.String()+`"}`, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *BackendTestSuite) TestCallRPCSendTransactionUpstream() {
|
||||||
|
nodeConfig, err := MakeTestNodeConfig(params.RopstenNetworkID)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
nodeConfig.UpstreamConfig.Enabled = true
|
||||||
|
nodeConfig.UpstreamConfig.URL = "https://ropsten.infura.io/nKmXgiFgc2KqtoQ8BCGJ"
|
||||||
|
|
||||||
|
nodeStarted, err := s.backend.StartNode(nodeConfig)
|
||||||
|
s.NoError(err)
|
||||||
|
defer s.backend.StopNode()
|
||||||
|
|
||||||
|
<-nodeStarted
|
||||||
|
|
||||||
|
// Allow to sync the blockchain.
|
||||||
|
time.Sleep(TestConfig.Node.SyncSeconds * time.Second)
|
||||||
|
|
||||||
|
err = s.backend.AccountManager().SelectAccount(TestConfig.Account2.Address, TestConfig.Account2.Password)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
transactionCompleted := make(chan struct{})
|
||||||
|
|
||||||
|
var txHash gethcommon.Hash
|
||||||
|
node.SetDefaultNodeNotificationHandler(func(rawSignal string) {
|
||||||
|
var signal node.SignalEnvelope
|
||||||
|
err := json.Unmarshal([]byte(rawSignal), &signal)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
if signal.Type == node.EventTransactionQueued {
|
||||||
|
event := signal.Event.(map[string]interface{})
|
||||||
|
txID := event["id"].(string)
|
||||||
|
txHash, err = s.backend.CompleteTransaction(common.QueuedTxID(txID), TestConfig.Account2.Password)
|
||||||
|
s.NoError(err, "cannot complete queued transaction %s", txID)
|
||||||
|
|
||||||
|
close(transactionCompleted)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
result := s.backend.CallRPC(`{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"method": "eth_sendTransaction",
|
||||||
|
"params": [{
|
||||||
|
"from": "` + TestConfig.Account2.Address + `",
|
||||||
|
"to": "` + TestConfig.Account1.Address + `",
|
||||||
|
"value": "0x9184e72a"
|
||||||
|
}]
|
||||||
|
}`)
|
||||||
|
s.NotContains(result, "error")
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-transactionCompleted:
|
||||||
|
case <-time.After(time.Minute):
|
||||||
|
s.FailNow("sending transaction timed out")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Equal(`{"jsonrpc":"2.0","id":1,"result":"`+txHash.String()+`"}`, result)
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME(tiabc): There's also a test with the same name in geth/node/manager_test.go
|
// FIXME(tiabc): There's also a test with the same name in geth/node/manager_test.go
|
||||||
// so this test should only check StatusBackend logic with a mocked version of the underlying NodeManager.
|
// so this test should only check StatusBackend logic with a mocked version of the underlying NodeManager.
|
||||||
func (s *BackendTestSuite) TestRaceConditions() {
|
func (s *BackendTestSuite) TestRaceConditions() {
|
||||||
|
|
|
@ -133,3 +133,28 @@ func (r RPCCall) ParseGasPrice() *hexutil.Big {
|
||||||
|
|
||||||
return (*hexutil.Big)(parsedValue)
|
return (*hexutil.Big)(parsedValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToSendTxArgs converts RPCCall to SendTxArgs.
|
||||||
|
func (r RPCCall) ToSendTxArgs() SendTxArgs {
|
||||||
|
var err error
|
||||||
|
var fromAddr, toAddr gethcommon.Address
|
||||||
|
|
||||||
|
fromAddr, err = r.ParseFromAddress()
|
||||||
|
if err != nil {
|
||||||
|
fromAddr = gethcommon.HexToAddress("0x0")
|
||||||
|
}
|
||||||
|
|
||||||
|
toAddr, err = r.ParseToAddress()
|
||||||
|
if err != nil {
|
||||||
|
toAddr = gethcommon.HexToAddress("0x0")
|
||||||
|
}
|
||||||
|
|
||||||
|
return SendTxArgs{
|
||||||
|
To: &toAddr,
|
||||||
|
From: fromAddr,
|
||||||
|
Value: r.ParseValue(),
|
||||||
|
Data: r.ParseData(),
|
||||||
|
Gas: r.ParseGas(),
|
||||||
|
GasPrice: r.ParseGasPrice(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -125,8 +125,11 @@ type AccountManager interface {
|
||||||
// Logout clears whisper identities
|
// Logout clears whisper identities
|
||||||
Logout() error
|
Logout() error
|
||||||
|
|
||||||
// AccountsListRequestHandler returns handler to process account list request
|
// Accounts returns handler to process account list request
|
||||||
AccountsListRequestHandler() func(entities []common.Address) []common.Address
|
Accounts() ([]common.Address, error)
|
||||||
|
|
||||||
|
// AccountsRPCHandler returns RPC wrapper for Accounts()
|
||||||
|
AccountsRPCHandler() rpc.Handler
|
||||||
|
|
||||||
// AddressToDecryptedAccount tries to load decrypted key for a given account.
|
// AddressToDecryptedAccount tries to load decrypted key for a given account.
|
||||||
// The running node, has a keystore directory which is loaded on start. Key file
|
// The running node, has a keystore directory which is loaded on start. Key file
|
||||||
|
@ -224,6 +227,8 @@ type TxQueueManager interface {
|
||||||
// TODO(adam): might be not needed
|
// TODO(adam): might be not needed
|
||||||
SetTransactionReturnHandler(fn EnqueuedTxReturnHandler)
|
SetTransactionReturnHandler(fn EnqueuedTxReturnHandler)
|
||||||
|
|
||||||
|
SendTransactionRPCHandler(ctx context.Context, args ...interface{}) (interface{}, error)
|
||||||
|
|
||||||
// TransactionReturnHandler returns handler that processes responses from internal tx manager
|
// TransactionReturnHandler returns handler that processes responses from internal tx manager
|
||||||
TransactionReturnHandler() func(queuedTx *QueuedTx, err error)
|
TransactionReturnHandler() func(queuedTx *QueuedTx, err error)
|
||||||
|
|
||||||
|
|
|
@ -348,16 +348,29 @@ func (mr *MockAccountManagerMockRecorder) Logout() *gomock.Call {
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Logout", reflect.TypeOf((*MockAccountManager)(nil).Logout))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Logout", reflect.TypeOf((*MockAccountManager)(nil).Logout))
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountsListRequestHandler mocks base method
|
// Accounts mocks base method
|
||||||
func (m *MockAccountManager) AccountsListRequestHandler() func([]common.Address) []common.Address {
|
func (m *MockAccountManager) Accounts() ([]common.Address, error) {
|
||||||
ret := m.ctrl.Call(m, "AccountsListRequestHandler")
|
ret := m.ctrl.Call(m, "Accounts")
|
||||||
ret0, _ := ret[0].(func([]common.Address) []common.Address)
|
ret0, _ := ret[0].([]common.Address)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accounts indicates an expected call of Accounts
|
||||||
|
func (mr *MockAccountManagerMockRecorder) Accounts() *gomock.Call {
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Accounts", reflect.TypeOf((*MockAccountManager)(nil).Accounts))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountsRPCHandler mocks base method
|
||||||
|
func (m *MockAccountManager) AccountsRPCHandler() rpc.Handler {
|
||||||
|
ret := m.ctrl.Call(m, "AccountsRPCHandler")
|
||||||
|
ret0, _ := ret[0].(rpc.Handler)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountsListRequestHandler indicates an expected call of AccountsListRequestHandler
|
// AccountsRPCHandler indicates an expected call of AccountsRPCHandler
|
||||||
func (mr *MockAccountManagerMockRecorder) AccountsListRequestHandler() *gomock.Call {
|
func (mr *MockAccountManagerMockRecorder) AccountsRPCHandler() *gomock.Call {
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AccountsListRequestHandler", reflect.TypeOf((*MockAccountManager)(nil).AccountsListRequestHandler))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AccountsRPCHandler", reflect.TypeOf((*MockAccountManager)(nil).AccountsRPCHandler))
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddressToDecryptedAccount mocks base method
|
// AddressToDecryptedAccount mocks base method
|
||||||
|
|
|
@ -3,7 +3,6 @@ package jail
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
|
||||||
gethrpc "github.com/ethereum/go-ethereum/rpc"
|
gethrpc "github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/status-im/status-go/geth/common"
|
"github.com/status-im/status-go/geth/common"
|
||||||
"github.com/status-im/status-go/geth/jail/internal/vm"
|
"github.com/status-im/status-go/geth/jail/internal/vm"
|
||||||
|
@ -32,50 +31,10 @@ func NewExecutionPolicy(
|
||||||
|
|
||||||
// Execute handles the execution of a RPC request and routes appropriately to either a local or remote ethereum node.
|
// Execute handles the execution of a RPC request and routes appropriately to either a local or remote ethereum node.
|
||||||
func (ep *ExecutionPolicy) Execute(req common.RPCCall, vm *vm.VM) (map[string]interface{}, error) {
|
func (ep *ExecutionPolicy) Execute(req common.RPCCall, vm *vm.VM) (map[string]interface{}, error) {
|
||||||
if params.SendTransactionMethodName == req.Method {
|
|
||||||
return ep.executeSendTransaction(vm, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
client := ep.nodeManager.RPCClient()
|
client := ep.nodeManager.RPCClient()
|
||||||
|
|
||||||
return ep.executeWithClient(client, vm, req)
|
return ep.executeWithClient(client, vm, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// executeRemoteSendTransaction defines a function to execute RPC method eth_sendTransaction over the upstream server.
|
|
||||||
func (ep *ExecutionPolicy) executeSendTransaction(vm *vm.VM, req common.RPCCall) (map[string]interface{}, error) {
|
|
||||||
messageID, err := preProcessRequest(vm)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(adam): check if context is used
|
|
||||||
ctx := context.WithValue(context.Background(), common.MessageIDKey, messageID)
|
|
||||||
args := sendTxArgsFromRPCCall(req)
|
|
||||||
|
|
||||||
tx := ep.txQueueManager.CreateTransaction(ctx, args)
|
|
||||||
|
|
||||||
if err := ep.txQueueManager.QueueTransaction(tx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ep.txQueueManager.WaitForTransaction(tx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// invoke post processing
|
|
||||||
postProcessRequest(vm, req, messageID)
|
|
||||||
|
|
||||||
res := map[string]interface{}{
|
|
||||||
"jsonrpc": "2.0",
|
|
||||||
"id": req.ID,
|
|
||||||
// @TODO(adam): which one is actually used?
|
|
||||||
"result": tx.Hash.Hex(),
|
|
||||||
"hash": tx.Hash.Hex(),
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ep *ExecutionPolicy) executeWithClient(client *rpc.Client, vm *vm.VM, req common.RPCCall) (map[string]interface{}, error) {
|
func (ep *ExecutionPolicy) executeWithClient(client *rpc.Client, vm *vm.VM, req common.RPCCall) (map[string]interface{}, error) {
|
||||||
// Arbitrary JSON-RPC response.
|
// Arbitrary JSON-RPC response.
|
||||||
var result interface{}
|
var result interface{}
|
||||||
|
@ -96,7 +55,9 @@ func (ep *ExecutionPolicy) executeWithClient(client *rpc.Client, vm *vm.VM, req
|
||||||
if client == nil {
|
if client == nil {
|
||||||
resp = newErrorResponse("RPC client is not available. Node is stopped?", &req.ID)
|
resp = newErrorResponse("RPC client is not available. Node is stopped?", &req.ID)
|
||||||
} else {
|
} else {
|
||||||
err = client.Call(&result, req.Method, req.Params...)
|
// TODO(adam): check if context is used
|
||||||
|
ctx := context.WithValue(context.Background(), common.MessageIDKey, messageID)
|
||||||
|
err = client.CallContext(ctx, &result, req.Method, req.Params...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err2, ok := err.(gethrpc.Error); ok {
|
if err2, ok := err.(gethrpc.Error); ok {
|
||||||
resp["error"] = map[string]interface{}{
|
resp["error"] = map[string]interface{}{
|
||||||
|
@ -150,32 +111,3 @@ func currentMessageID(vm *vm.VM) string {
|
||||||
}
|
}
|
||||||
return msgID.String()
|
return msgID.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendTxArgsFromRPCCall(req common.RPCCall) common.SendTxArgs {
|
|
||||||
// no need to persist extra state for other requests
|
|
||||||
if req.Method != params.SendTransactionMethodName {
|
|
||||||
return common.SendTxArgs{}
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
var fromAddr, toAddr gethcommon.Address
|
|
||||||
|
|
||||||
fromAddr, err = req.ParseFromAddress()
|
|
||||||
if err != nil {
|
|
||||||
fromAddr = gethcommon.HexToAddress("0x0")
|
|
||||||
}
|
|
||||||
|
|
||||||
toAddr, err = req.ParseToAddress()
|
|
||||||
if err != nil {
|
|
||||||
toAddr = gethcommon.HexToAddress("0x0")
|
|
||||||
}
|
|
||||||
|
|
||||||
return common.SendTxArgs{
|
|
||||||
To: &toAddr,
|
|
||||||
From: fromAddr,
|
|
||||||
Value: req.ParseValue(),
|
|
||||||
Data: req.ParseData(),
|
|
||||||
Gas: req.ParseGas(),
|
|
||||||
GasPrice: req.ParseGasPrice(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package node
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -14,6 +15,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/status-im/status-go/extkeys"
|
"github.com/status-im/status-go/extkeys"
|
||||||
"github.com/status-im/status-go/geth/common"
|
"github.com/status-im/status-go/geth/common"
|
||||||
|
"github.com/status-im/status-go/geth/rpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// errors
|
// errors
|
||||||
|
@ -303,31 +305,49 @@ func (m *AccountManager) importExtendedKey(extKey *extkeys.ExtendedKey, password
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountsListRequestHandler returns handler to process account list request
|
// Accounts returns list of addresses for selected account, including
|
||||||
func (m *AccountManager) AccountsListRequestHandler() func(entities []gethcommon.Address) []gethcommon.Address {
|
// subaccounts.
|
||||||
return func(entities []gethcommon.Address) []gethcommon.Address {
|
func (m *AccountManager) Accounts() ([]gethcommon.Address, error) {
|
||||||
if m.selectedAccount == nil {
|
am, err := m.nodeManager.AccountManager()
|
||||||
return []gethcommon.Address{}
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var addresses []gethcommon.Address
|
||||||
|
for _, wallet := range am.Wallets() {
|
||||||
|
for _, account := range wallet.Accounts() {
|
||||||
|
addresses = append(addresses, account.Address)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m.refreshSelectedAccount()
|
if m.selectedAccount == nil {
|
||||||
|
return []gethcommon.Address{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
filtered := make([]gethcommon.Address, 0)
|
m.refreshSelectedAccount()
|
||||||
for _, account := range entities {
|
|
||||||
// main account
|
filtered := make([]gethcommon.Address, 0)
|
||||||
if m.selectedAccount.Address.Hex() == account.Hex() {
|
for _, account := range addresses {
|
||||||
filtered = append(filtered, account)
|
// main account
|
||||||
} else {
|
if m.selectedAccount.Address.Hex() == account.Hex() {
|
||||||
// sub accounts
|
filtered = append(filtered, account)
|
||||||
for _, subAccount := range m.selectedAccount.SubAccounts {
|
} else {
|
||||||
if subAccount.Address.Hex() == account.Hex() {
|
// sub accounts
|
||||||
filtered = append(filtered, account)
|
for _, subAccount := range m.selectedAccount.SubAccounts {
|
||||||
}
|
if subAccount.Address.Hex() == account.Hex() {
|
||||||
|
filtered = append(filtered, account)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return filtered
|
return filtered, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountsRPCHandler returns RPC Handler for the Accounts() method.
|
||||||
|
func (m *AccountManager) AccountsRPCHandler() rpc.Handler {
|
||||||
|
return func(context.Context, ...interface{}) (interface{}, error) {
|
||||||
|
return m.Accounts()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -225,18 +225,42 @@ func (m *TxQueueManager) completeRemoteTransaction(queuedTx *common.QueuedTx, pa
|
||||||
return emptyHash, err
|
return emptyHash, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
args := queuedTx.Args
|
||||||
|
|
||||||
|
if args.GasPrice == nil {
|
||||||
|
value, err := m.gasPrice()
|
||||||
|
if err != nil {
|
||||||
|
return emptyHash, err
|
||||||
|
}
|
||||||
|
|
||||||
|
args.GasPrice = value
|
||||||
|
}
|
||||||
|
|
||||||
chainID := big.NewInt(int64(config.NetworkID))
|
chainID := big.NewInt(int64(config.NetworkID))
|
||||||
nonce := uint64(txCount)
|
nonce := uint64(txCount)
|
||||||
gasPrice := (*big.Int)(queuedTx.Args.GasPrice)
|
gasPrice := (*big.Int)(args.GasPrice)
|
||||||
dataVal := []byte(queuedTx.Args.Data)
|
data := []byte(args.Data)
|
||||||
priceVal := (*big.Int)(queuedTx.Args.Value)
|
value := (*big.Int)(args.Value)
|
||||||
|
toAddr := gethcommon.Address{}
|
||||||
|
if args.To != nil {
|
||||||
|
toAddr = *args.To
|
||||||
|
}
|
||||||
|
|
||||||
gas, err := m.estimateGas(queuedTx.Args)
|
gas, err := m.estimateGas(args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return emptyHash, err
|
return emptyHash, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tx := types.NewTransaction(nonce, *queuedTx.Args.To, priceVal, (*big.Int)(gas), gasPrice, dataVal)
|
log.Info(
|
||||||
|
"preparing raw transaction",
|
||||||
|
"from", args.From.Hex(),
|
||||||
|
"to", toAddr.Hex(),
|
||||||
|
"gas", gas,
|
||||||
|
"gasPrice", gasPrice,
|
||||||
|
"value", value,
|
||||||
|
)
|
||||||
|
|
||||||
|
tx := types.NewTransaction(nonce, toAddr, value, (*big.Int)(gas), gasPrice, data)
|
||||||
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), selectedAcct.AccountKey.PrivateKey)
|
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), selectedAcct.AccountKey.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return emptyHash, err
|
return emptyHash, err
|
||||||
|
@ -305,6 +329,20 @@ func (m *TxQueueManager) estimateGas(args common.SendTxArgs) (*hexutil.Big, erro
|
||||||
return &estimatedGas, nil
|
return &estimatedGas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *TxQueueManager) gasPrice() (*hexutil.Big, error) {
|
||||||
|
client := m.nodeManager.RPCClient()
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var gasPrice hexutil.Big
|
||||||
|
if err := client.CallContext(ctx, &gasPrice, "eth_gasPrice"); err != nil {
|
||||||
|
log.Warn("failed to get gas price", "err", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gasPrice, nil
|
||||||
|
}
|
||||||
|
|
||||||
// CompleteTransactions instructs backend to complete sending of multiple transactions
|
// CompleteTransactions instructs backend to complete sending of multiple transactions
|
||||||
func (m *TxQueueManager) CompleteTransactions(ids []common.QueuedTxID, password string) map[common.QueuedTxID]common.RawCompleteTransactionResult {
|
func (m *TxQueueManager) CompleteTransactions(ids []common.QueuedTxID, password string) map[common.QueuedTxID]common.RawCompleteTransactionResult {
|
||||||
results := make(map[common.QueuedTxID]common.RawCompleteTransactionResult)
|
results := make(map[common.QueuedTxID]common.RawCompleteTransactionResult)
|
||||||
|
@ -430,3 +468,25 @@ func (m *TxQueueManager) sendTransactionErrorCode(err error) string {
|
||||||
func (m *TxQueueManager) SetTransactionReturnHandler(fn common.EnqueuedTxReturnHandler) {
|
func (m *TxQueueManager) SetTransactionReturnHandler(fn common.EnqueuedTxReturnHandler) {
|
||||||
m.txQueue.SetTxReturnHandler(fn)
|
m.txQueue.SetTxReturnHandler(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendTransactionRPCHandler is a handler for eth_sendTransaction method.
|
||||||
|
// It accepts one param which is a slice with a map of transaction params.
|
||||||
|
func (m *TxQueueManager) SendTransactionRPCHandler(ctx context.Context, args ...interface{}) (interface{}, error) {
|
||||||
|
log.Info("SendTransactionRPCHandler called")
|
||||||
|
|
||||||
|
// TODO(adam): it's a hack to parse arguments as common.RPCCall can do that.
|
||||||
|
// We should refactor parsing these params to a separate struct.
|
||||||
|
rpcCall := common.RPCCall{Params: args}
|
||||||
|
|
||||||
|
tx := m.CreateTransaction(ctx, rpcCall.ToSendTxArgs())
|
||||||
|
|
||||||
|
if err := m.QueueTransaction(tx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.WaitForTransaction(tx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.Hash.Hex(), nil
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,11 @@ package rpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
"github.com/status-im/status-go/geth/params"
|
"github.com/status-im/status-go/geth/params"
|
||||||
|
@ -10,6 +14,9 @@ import (
|
||||||
gethrpc "github.com/ethereum/go-ethereum/rpc"
|
gethrpc "github.com/ethereum/go-ethereum/rpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Handler defines handler for RPC methods.
|
||||||
|
type Handler func(context.Context, ...interface{}) (interface{}, error)
|
||||||
|
|
||||||
// Client represents RPC client with custom routing
|
// Client represents RPC client with custom routing
|
||||||
// scheme. It automatically decides where RPC call
|
// scheme. It automatically decides where RPC call
|
||||||
// goes - Upstream or Local node.
|
// goes - Upstream or Local node.
|
||||||
|
@ -21,6 +28,9 @@ type Client struct {
|
||||||
upstream *gethrpc.Client
|
upstream *gethrpc.Client
|
||||||
|
|
||||||
router *router
|
router *router
|
||||||
|
|
||||||
|
handlersMx sync.RWMutex // mx guards handlers
|
||||||
|
handlers map[string]Handler // locally registered handlers
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient initializes Client and tries to connect to both,
|
// NewClient initializes Client and tries to connect to both,
|
||||||
|
@ -29,7 +39,9 @@ type Client struct {
|
||||||
// Client is safe for concurrent use and will automatically
|
// Client is safe for concurrent use and will automatically
|
||||||
// reconnect to the server if connection is lost.
|
// reconnect to the server if connection is lost.
|
||||||
func NewClient(node *node.Node, upstream params.UpstreamRPCConfig) (*Client, error) {
|
func NewClient(node *node.Node, upstream params.UpstreamRPCConfig) (*Client, error) {
|
||||||
c := &Client{}
|
c := &Client{
|
||||||
|
handlers: make(map[string]Handler),
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
c.local, err = node.Attach()
|
c.local, err = node.Attach()
|
||||||
|
@ -72,8 +84,92 @@ func (c *Client) Call(result interface{}, method string, args ...interface{}) er
|
||||||
//
|
//
|
||||||
// It uses custom routing scheme for calls.
|
// It uses custom routing scheme for calls.
|
||||||
func (c *Client) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error {
|
func (c *Client) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error {
|
||||||
|
// check locally registered handlers first
|
||||||
|
if handler, ok := c.handler(method); ok {
|
||||||
|
return c.callMethod(ctx, result, handler, args...)
|
||||||
|
}
|
||||||
|
|
||||||
if c.router.routeRemote(method) {
|
if c.router.routeRemote(method) {
|
||||||
return c.upstream.CallContext(ctx, result, method, args...)
|
return c.upstream.CallContext(ctx, result, method, args...)
|
||||||
}
|
}
|
||||||
return c.local.CallContext(ctx, result, method, args...)
|
return c.local.CallContext(ctx, result, method, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegisterHandler registers local handler for specific RPC method.
|
||||||
|
//
|
||||||
|
// If method is registered, it will be executed with given handler and
|
||||||
|
// never routed to the upstream or local servers.
|
||||||
|
func (c *Client) RegisterHandler(method string, handler Handler) {
|
||||||
|
c.handlersMx.Lock()
|
||||||
|
defer c.handlersMx.Unlock()
|
||||||
|
|
||||||
|
c.handlers[method] = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// callMethod calls registered RPC handler with given args and pointer to result.
|
||||||
|
// It handles proper params and result converting
|
||||||
|
//
|
||||||
|
// TODO(divan): use cancellation via context here?
|
||||||
|
func (c *Client) callMethod(ctx context.Context, result interface{}, handler Handler, args ...interface{}) error {
|
||||||
|
response, err := handler(ctx, args...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if result is nil, just ignore result -
|
||||||
|
// the same way as gethrpc.CallContext() caller would expect
|
||||||
|
if result == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := setResultFromRPCResponse(result, response); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handler is a concurrently safe method to get registered handler by name.
|
||||||
|
func (c *Client) handler(method string) (Handler, bool) {
|
||||||
|
c.handlersMx.RLock()
|
||||||
|
defer c.handlersMx.RUnlock()
|
||||||
|
handler, ok := c.handlers[method]
|
||||||
|
return handler, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// setResultFromRPCResponse tries to set result value from response using reflection
|
||||||
|
// as concrete types are unknown.
|
||||||
|
func setResultFromRPCResponse(result, response interface{}) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = fmt.Errorf("invalid result type: %s", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
responseValue := reflect.ValueOf(response)
|
||||||
|
|
||||||
|
// If it is called via CallRaw, result has type json.RawMessage and
|
||||||
|
// we should marshal the response before setting it.
|
||||||
|
// Otherwise, it is called with CallContext and result is of concrete type,
|
||||||
|
// thus we should try to set it as it is.
|
||||||
|
// If response type and result type are incorrect, an error should be returned.
|
||||||
|
// TODO(divan): add additional checks for result underlying value, if needed:
|
||||||
|
// some example: https://golang.org/src/encoding/json/decode.go#L596
|
||||||
|
switch reflect.ValueOf(result).Elem().Type() {
|
||||||
|
case reflect.TypeOf(json.RawMessage{}), reflect.TypeOf([]byte{}):
|
||||||
|
data, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
responseValue = reflect.ValueOf(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
value := reflect.ValueOf(result).Elem()
|
||||||
|
if !value.CanSet() {
|
||||||
|
return errors.New("can't assign value to result")
|
||||||
|
}
|
||||||
|
value.Set(responseValue)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
package rpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSetResultFromRPCResponse(t *testing.T) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
var resultRawMessage json.RawMessage
|
||||||
|
err = setResultFromRPCResponse(&resultRawMessage, []string{"one", "two", "three"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, json.RawMessage(`["one","two","three"]`), resultRawMessage)
|
||||||
|
|
||||||
|
var resultSlice []int
|
||||||
|
err = setResultFromRPCResponse(&resultSlice, []int{1, 2, 3})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, []int{1, 2, 3}, resultSlice)
|
||||||
|
|
||||||
|
var resultMap map[string]interface{}
|
||||||
|
err = setResultFromRPCResponse(&resultMap, map[string]interface{}{"test": true})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, map[string]interface{}{"test": true}, resultMap)
|
||||||
|
|
||||||
|
var resultStruct struct {
|
||||||
|
A int
|
||||||
|
B string
|
||||||
|
}
|
||||||
|
err = setResultFromRPCResponse(&resultStruct, struct {
|
||||||
|
A int
|
||||||
|
B string
|
||||||
|
}{5, "test"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, struct {
|
||||||
|
A int
|
||||||
|
B string
|
||||||
|
}{5, "test"}, resultStruct)
|
||||||
|
|
||||||
|
var resultIncorrectType []int
|
||||||
|
err = setResultFromRPCResponse(&resultIncorrectType, []string{"a", "b"})
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
|
@ -1,11 +1,13 @@
|
||||||
package rpc_test
|
package rpc_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/status-im/status-go/geth/node"
|
"github.com/status-im/status-go/geth/node"
|
||||||
"github.com/status-im/status-go/geth/params"
|
"github.com/status-im/status-go/geth/params"
|
||||||
"github.com/status-im/status-go/geth/rpc"
|
"github.com/status-im/status-go/geth/rpc"
|
||||||
|
@ -242,3 +244,40 @@ func (s *RPCTestSuite) TestCallRPC() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCallRawResult checks if returned response is a valid JSON-RPC response.
|
||||||
|
func (s *RPCTestSuite) TestCallRawResult() {
|
||||||
|
nodeConfig, err := MakeTestNodeConfig(params.RopstenNetworkID)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
nodeStarted, err := s.NodeManager.StartNode(nodeConfig)
|
||||||
|
s.NoError(err)
|
||||||
|
defer s.NodeManager.StopNode()
|
||||||
|
|
||||||
|
<-nodeStarted
|
||||||
|
|
||||||
|
client := s.NodeManager.RPCClient()
|
||||||
|
|
||||||
|
jsonResult := client.CallRaw(`{"jsonrpc":"2.0","method":"shh_version","params":[],"id":67}`)
|
||||||
|
s.Equal(`{"jsonrpc":"2.0","id":67,"result":"5.0"}`, jsonResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCallContextResult checks if result passed to CallContext
|
||||||
|
// is set accordingly to its underlying memory layout.
|
||||||
|
func (s *RPCTestSuite) TestCallContextResult() {
|
||||||
|
nodeConfig, err := MakeTestNodeConfig(params.RopstenNetworkID)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
nodeStarted, err := s.NodeManager.StartNode(nodeConfig)
|
||||||
|
s.NoError(err)
|
||||||
|
defer s.NodeManager.StopNode()
|
||||||
|
|
||||||
|
<-nodeStarted
|
||||||
|
|
||||||
|
client := s.NodeManager.RPCClient()
|
||||||
|
|
||||||
|
var blockNumber hexutil.Uint
|
||||||
|
err = client.CallContext(context.Background(), &blockNumber, "eth_blockNumber")
|
||||||
|
s.NoError(err)
|
||||||
|
s.True(blockNumber > 0, "blockNumber should be higher than 0")
|
||||||
|
}
|
||||||
|
|
|
@ -35,7 +35,8 @@ func (r *router) routeRemote(method string) bool {
|
||||||
// remoteMethods contains methods that should be routed to
|
// remoteMethods contains methods that should be routed to
|
||||||
// the upstream node; the rest is considered to be routed to
|
// the upstream node; the rest is considered to be routed to
|
||||||
// the local node.
|
// the local node.
|
||||||
// TODO(tiabc): Write a test on each of these methods to ensure they're all routed to the proper node and ensure they really work.
|
// TODO(tiabc): Write a test on each of these methods to ensure they're all routed to the proper node and ensure they really work
|
||||||
|
// TODO(tiabc: as we already caught https://github.com/status-im/status-go/issues/350 as the result of missing such test.
|
||||||
// Although it's tempting to only list methods coming to the local node as there're fewer of them
|
// Although it's tempting to only list methods coming to the local node as there're fewer of them
|
||||||
// but it's deceptive: we want to ensure that only known requests leave our zone of responsibility.
|
// but it's deceptive: we want to ensure that only known requests leave our zone of responsibility.
|
||||||
// Also, we want new requests in newer Geth versions not to be accidentally routed to the upstream.
|
// Also, we want new requests in newer Geth versions not to be accidentally routed to the upstream.
|
||||||
|
@ -47,7 +48,7 @@ var remoteMethods = [...]string{
|
||||||
"eth_mining",
|
"eth_mining",
|
||||||
"eth_hashrate",
|
"eth_hashrate",
|
||||||
"eth_gasPrice",
|
"eth_gasPrice",
|
||||||
//"eth_accounts", // goes to the local because we handle sub-accounts
|
//"eth_accounts", // due to sub-accounts handling
|
||||||
"eth_blockNumber",
|
"eth_blockNumber",
|
||||||
"eth_getBalance",
|
"eth_getBalance",
|
||||||
"eth_getStorageAt",
|
"eth_getStorageAt",
|
||||||
|
@ -57,8 +58,8 @@ var remoteMethods = [...]string{
|
||||||
"eth_getUncleCountByBlockHash",
|
"eth_getUncleCountByBlockHash",
|
||||||
"eth_getUncleCountByBlockNumber",
|
"eth_getUncleCountByBlockNumber",
|
||||||
"eth_getCode",
|
"eth_getCode",
|
||||||
//"eth_sign", // goes to the local because only the local node has an injected account to sign the payload with
|
//"eth_sign", // only the local node has an injected account to sign the payload with
|
||||||
"eth_sendTransaction",
|
//"eth_sendTransaction", // we handle this specially calling eth_estimateGas, signing it locally and sending eth_sendRawTransaction afterwards
|
||||||
"eth_sendRawTransaction",
|
"eth_sendRawTransaction",
|
||||||
"eth_call",
|
"eth_call",
|
||||||
"eth_estimateGas",
|
"eth_estimateGas",
|
||||||
|
|
|
@ -12,12 +12,12 @@ func TestRouteWithUpstream(t *testing.T) {
|
||||||
router := newRouter(true)
|
router := newRouter(true)
|
||||||
|
|
||||||
for _, method := range remoteMethods {
|
for _, method := range remoteMethods {
|
||||||
require.True(t, router.routeRemote(method))
|
require.True(t, router.routeRemote(method), "method "+method+" should routed to remote")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, method := range localMethods {
|
for _, method := range localMethods {
|
||||||
t.Run(method, func(t *testing.T) {
|
t.Run(method, func(t *testing.T) {
|
||||||
require.False(t, router.routeRemote(method))
|
require.False(t, router.routeRemote(method), "method "+method+" should routed to local")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,10 +26,10 @@ func TestRouteWithoutUpstream(t *testing.T) {
|
||||||
router := newRouter(false)
|
router := newRouter(false)
|
||||||
|
|
||||||
for _, method := range remoteMethods {
|
for _, method := range remoteMethods {
|
||||||
require.True(t, router.routeRemote(method))
|
require.False(t, router.routeRemote(method), "method "+method+" should routed to locally without UpstreamEnabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, method := range localMethods {
|
for _, method := range localMethods {
|
||||||
require.True(t, router.routeRemote(method))
|
require.False(t, router.routeRemote(method), "method "+method+" should routed to local")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue