fix(connector)_: connector response structure to valid json (#5625)

This commit is contained in:
Mikhail Rogachev 2024-08-02 12:43:53 +04:00 committed by GitHub
parent a57055f43b
commit 7e87fd0d05
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 111 additions and 81 deletions

View File

@ -1,10 +1,18 @@
package connector
import (
"encoding/json"
"errors"
"fmt"
"github.com/status-im/status-go/services/connector/commands"
persistence "github.com/status-im/status-go/services/connector/database"
)
var (
ErrInvalidResponseFromForwardedRpc = errors.New("invalid response from forwarded RPC")
)
type API struct {
s *Service
r *CommandRegistry
@ -47,7 +55,37 @@ func NewAPI(s *Service) *API {
}
}
func (api *API) CallRPC(inputJSON string) (string, error) {
func (api *API) forwardRPC(URL string, inputJSON string) (interface{}, error) {
dApp, err := persistence.SelectDAppByUrl(api.s.db, URL)
if err != nil {
return "", err
}
if dApp == nil {
return "", commands.ErrDAppIsNotPermittedByUser
}
var response map[string]interface{}
rawResponse := api.s.rpc.CallRaw(inputJSON)
if err := json.Unmarshal([]byte(rawResponse), &response); err != nil {
return "", err
}
if errorField, ok := response["error"]; ok {
errorMap, _ := errorField.(map[string]interface{})
errorCode, _ := errorMap["code"].(float64)
errorMessage, _ := errorMap["message"].(string)
return nil, fmt.Errorf("error code %v: %s", errorCode, errorMessage)
}
if result, ok := response["result"]; ok {
return result, nil
}
return nil, ErrInvalidResponseFromForwardedRpc
}
func (api *API) CallRPC(inputJSON string) (interface{}, error) {
request, err := commands.RPCRequestFromJSON(inputJSON)
if err != nil {
return "", err
@ -57,7 +95,7 @@ func (api *API) CallRPC(inputJSON string) (string, error) {
return command.Execute(request)
}
return api.s.rpc.CallRaw(inputJSON), nil
return api.forwardRPC(request.URL, inputJSON)
}
func (api *API) RecallDAppPermission(origin string) error {

View File

@ -2,8 +2,7 @@ package commands
import (
"database/sql"
"encoding/json"
"fmt"
"strings"
"github.com/status-im/status-go/eth-node/types"
persistence "github.com/status-im/status-go/services/connector/database"
@ -13,17 +12,11 @@ type AccountsCommand struct {
Db *sql.DB
}
func (c *AccountsCommand) dAppToAccountsResponse(dApp *persistence.DApp) (string, error) {
addresses := []types.Address{dApp.SharedAccount}
responseJSON, err := json.Marshal(addresses)
if err != nil {
return "", fmt.Errorf("failed to marshal response: %v", err)
}
return string(responseJSON), nil
func FormatAccountAddressToResponse(address types.Address) []string {
return []string{strings.ToLower(address.Hex())}
}
func (c *AccountsCommand) Execute(request RPCRequest) (string, error) {
func (c *AccountsCommand) Execute(request RPCRequest) (interface{}, error) {
err := request.Validate()
if err != nil {
return "", err
@ -38,5 +31,5 @@ func (c *AccountsCommand) Execute(request RPCRequest) (string, error) {
return "", ErrDAppIsNotPermittedByUser
}
return c.dAppToAccountsResponse(dApp)
return FormatAccountAddressToResponse(dApp.SharedAccount), nil
}

View File

@ -1,7 +1,6 @@
package commands
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
@ -52,14 +51,8 @@ func TestGetAccountForPermittedDApp(t *testing.T) {
request, err := ConstructRPCRequest("eth_accounts", []interface{}{}, &testDAppData)
assert.NoError(t, err)
expectedResponse := FormatAccountAddressToResponse(sharedAccount)
response, err := cmd.Execute(request)
assert.NoError(t, err)
// Unmarshal the response into a slice of addresses
var result []types.Address
err = json.Unmarshal([]byte(response), &result)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, sharedAccount, result[0])
assert.Equal(t, expectedResponse, response)
}

View File

@ -14,7 +14,7 @@ type ChainIDCommand struct {
Db *sql.DB
}
func (c *ChainIDCommand) Execute(request RPCRequest) (string, error) {
func (c *ChainIDCommand) Execute(request RPCRequest) (interface{}, error) {
err := request.Validate()
if err != nil {
return "", err

View File

@ -25,7 +25,7 @@ type RawAccountsResponse struct {
Result []accounts.Account `json:"result"`
}
func (c *RequestAccountsCommand) Execute(request RPCRequest) (string, error) {
func (c *RequestAccountsCommand) Execute(request RPCRequest) (interface{}, error) {
err := request.Validate()
if err != nil {
return "", err
@ -61,5 +61,5 @@ func (c *RequestAccountsCommand) Execute(request RPCRequest) (string, error) {
}
}
return c.dAppToAccountsResponse(dApp)
return FormatAccountAddressToResponse(dApp.SharedAccount), nil
}

View File

@ -86,16 +86,11 @@ func TestRequestAccountsAcceptedAndRequestAgain(t *testing.T) {
}
}))
expectedResponse := FormatAccountAddressToResponse(accountAddress)
response, err := cmd.Execute(request)
assert.NoError(t, err)
// Unmarshal the response into a slice of addresses
var result []types.Address
err = json.Unmarshal([]byte(response), &result)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, accountAddress, result[0])
assert.Equal(t, expectedResponse, response)
// Check dApp in the database
dApp, err := persistence.SelectDAppByUrl(db, request.URL)
@ -108,12 +103,7 @@ func TestRequestAccountsAcceptedAndRequestAgain(t *testing.T) {
// This should not invoke UI side
response, err = cmd.Execute(request)
assert.NoError(t, err)
err = json.Unmarshal([]byte(response), &result)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, accountAddress, result[0])
assert.Equal(t, expectedResponse, response)
}
func TestRequestAccountsRejected(t *testing.T) {
@ -150,5 +140,4 @@ func TestRequestAccountsRejected(t *testing.T) {
_, err = cmd.Execute(request)
assert.Equal(t, ErrRequestAccountsRejectedByUser, err)
}

View File

@ -1,30 +1,22 @@
package commands
import (
"encoding/json"
"errors"
"fmt"
"time"
)
type RequestPermissionsCommand struct {
}
type RequestPermissionsCommand struct{}
type Permission struct {
ParentCapability string `json:"parentCapability"`
Date string `json:"date"`
}
type PermissionsResponse struct {
JSONRPC string `json:"jsonrpc"`
ID int `json:"id"`
Result []Permission `json:"result"`
}
var (
ErrNoRequestPermissionsParamsFound = errors.New("no request permission params found")
ErrMultipleKeysFound = errors.New("Multiple methodNames found in request permissions params")
ErrInvalidParamType = errors.New("Invalid parameter type")
ErrMultipleKeysFound = errors.New("multiple methodNames found in request permissions params")
ErrInvalidParamType = errors.New("invalid parameter type")
)
func (r *RPCRequest) getRequestPermissionsParam() (string, error) {
@ -48,7 +40,7 @@ func (r *RPCRequest) getRequestPermissionsParam() (string, error) {
return "", ErrNoRequestPermissionsParamsFound
}
func (c *RequestPermissionsCommand) getPermissionResponse(methodName string) (string, error) {
func (c *RequestPermissionsCommand) getPermissionResponse(methodName string) Permission {
date := time.Now().UnixNano() / int64(time.Millisecond)
response := Permission{
@ -56,15 +48,10 @@ func (c *RequestPermissionsCommand) getPermissionResponse(methodName string) (st
Date: fmt.Sprintf("%d", date),
}
responseJSON, err := json.Marshal(response)
if err != nil {
return "", fmt.Errorf("failed to marshal response: %v", err)
}
return string(responseJSON), nil
return response
}
func (c *RequestPermissionsCommand) Execute(request RPCRequest) (string, error) {
func (c *RequestPermissionsCommand) Execute(request RPCRequest) (interface{}, error) {
err := request.Validate()
if err != nil {
return "", err
@ -75,5 +62,5 @@ func (c *RequestPermissionsCommand) Execute(request RPCRequest) (string, error)
return "", err
}
return c.getPermissionResponse(methodName)
return c.getPermissionResponse(methodName), nil
}

View File

@ -1,7 +1,6 @@
package commands
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
@ -95,10 +94,11 @@ func TestRequestPermissionsResponse(t *testing.T) {
} else {
assert.NoError(t, err)
var permission Permission
err = json.Unmarshal([]byte(response), &permission)
assert.NoError(t, err)
assert.Equal(t, permission.ParentCapability, tc.expectedCapability)
if permission, ok := response.(Permission); ok {
assert.Equal(t, permission.ParentCapability, tc.expectedCapability)
} else {
assert.Fail(t, "Can't parse permission from the response")
}
}
})
}

View File

@ -29,7 +29,7 @@ type RPCRequest struct {
}
type RPCCommand interface {
Execute(request RPCRequest) (string, error)
Execute(request RPCRequest) (interface{}, error)
}
type RequestAccountsAcceptedArgs struct {

View File

@ -45,7 +45,7 @@ func (r *RPCRequest) getSendTransactionParams() (*transactions.SendTxArgs, error
return &sendTxArgs, nil
}
func (c *SendTransactionCommand) Execute(request RPCRequest) (string, error) {
func (c *SendTransactionCommand) Execute(request RPCRequest) (interface{}, error) {
err := request.Validate()
if err != nil {
return "", err

View File

@ -52,7 +52,7 @@ func (c *SwitchEthereumChainCommand) getSupportedChainIDs() ([]uint64, error) {
return chainutils.GetSupportedChainIDs(c.NetworkManager)
}
func (c *SwitchEthereumChainCommand) Execute(request RPCRequest) (string, error) {
func (c *SwitchEthereumChainCommand) Execute(request RPCRequest) (interface{}, error) {
err := request.Validate()
if err != nil {
return "", err

View File

@ -3,14 +3,15 @@ package connector
import (
"encoding/json"
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/services/connector/chainutils"
"github.com/status-im/status-go/services/connector/commands"
walletCommon "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/signal"
)
@ -21,11 +22,11 @@ func TestRequestAccountsSwitchChainAndSendTransactionFlow(t *testing.T) {
nm := commands.NetworkManagerMock{}
nm.SetNetworks([]*params.Network{
{
ChainID: 0x1,
ChainID: walletCommon.EthereumMainnet,
Layer: 1,
},
{
ChainID: 0x5,
ChainID: walletCommon.OptimismMainnet,
Layer: 1,
},
})
@ -77,36 +78,65 @@ func TestRequestAccountsSwitchChainAndSendTransactionFlow(t *testing.T) {
// Request accounts, now for real
request = "{\"method\": \"eth_requestAccounts\", \"params\": [], \"url\": \"http://testDAppURL123\", \"name\": \"testDAppName\", \"iconUrl\": \"http://testDAppIconUrl\" }"
expectedResponse := strings.ToLower(fmt.Sprintf(`["%s"]`, accountAddress.Hex()))
response, err = api.CallRPC(request)
assert.NoError(t, err)
assert.Equal(t, expectedResponse, response)
assert.Equal(t, commands.FormatAccountAddressToResponse(accountAddress), response)
// Request to switch ethereum chain
expectedChainId := "0x5"
request = fmt.Sprintf("{\"method\": \"wallet_switchEthereumChain\", \"params\": [{\"chainId\": \"%s\"}], \"url\": \"http://testDAppURL123\", \"name\": \"testDAppName\", \"iconUrl\": \"http://testDAppIconUrl\" }", expectedChainId)
expectedResponse = expectedChainId
expectedChainID, err := chainutils.GetHexChainID(walletCommon.ChainID(walletCommon.EthereumMainnet).String())
assert.NoError(t, err)
request = fmt.Sprintf("{\"method\": \"wallet_switchEthereumChain\", \"params\": [{\"chainId\": \"%s\"}], \"url\": \"http://testDAppURL123\", \"name\": \"testDAppName\", \"iconUrl\": \"http://testDAppIconUrl\" }", expectedChainID)
response, err = api.CallRPC(request)
assert.NoError(t, err)
assert.Equal(t, expectedResponse, response)
assert.Equal(t, expectedChainID, response)
// Check if the chain was switched
request = "{\"method\": \"eth_chainId\", \"params\": [], \"url\": \"http://testDAppURL123\", \"name\": \"testDAppName\", \"iconUrl\": \"http://testDAppIconUrl\" }"
response, err = api.CallRPC(request)
assert.NoError(t, err)
assert.Equal(t, expectedResponse, response)
assert.Equal(t, expectedChainID, response)
// Check the account after switching chain
request = "{\"method\": \"eth_accounts\", \"params\": [], \"url\": \"http://testDAppURL123\", \"name\": \"testDAppName\", \"iconUrl\": \"http://testDAppIconUrl\" }"
expectedResponse = strings.ToLower(fmt.Sprintf(`["%s"]`, accountAddress.Hex()))
response, err = api.CallRPC(request)
assert.NoError(t, err)
assert.Equal(t, expectedResponse, response)
assert.Equal(t, commands.FormatAccountAddressToResponse(accountAddress), response)
// Send transaction
request = fmt.Sprintf("{\"method\": \"eth_sendTransaction\", \"params\":[{\"from\":\"%s\",\"to\":\"0x0200000000000000000000000000000000000000\",\"value\":\"0x12345\",\"data\":\"0x307830\"}], \"url\": \"http://testDAppURL123\", \"name\": \"testDAppName\", \"iconUrl\": \"http://testDAppIconUrl\" }", accountAddress.Hex())
expectedResponse = expectedHash.Hex()
response, err = api.CallRPC(request)
assert.NoError(t, err)
assert.Equal(t, expectedHash.Hex(), response)
}
func TestForwardedRPCs(t *testing.T) {
db, close := createDB(t)
defer close()
rpc := commands.RPCClientMock{}
service := NewService(db, &rpc, nil)
api := NewAPI(service)
sharedAccount := types.BytesToAddress(types.FromHex("0x3d0ab2a774b74bb1d36f97700315adf962c69fct"))
testDAppData := signal.ConnectorDApp{
URL: "https://app.test.org",
Name: "testDAppName",
IconURL: "https://app.test.icon.org",
}
request := "{\"method\": \"eth_blockNumber\", \"params\":[],\"url\":\"https://app.test.org\",\"name\":\"testDAppName\",\"iconUrl\":\"http://testDAppIconUrl\"}"
_, err := api.CallRPC(request)
assert.Equal(t, commands.ErrDAppIsNotPermittedByUser, err)
err = commands.PersistDAppData(db, testDAppData, sharedAccount, 0x123)
assert.NoError(t, err)
expectedResponse := "0xaa37dc"
rpc.SetResponse(fmt.Sprintf(`{"jsonrpc":"2.0","id":37,"result":"%s"}`, expectedResponse))
response, err := api.CallRPC(request)
assert.NoError(t, err)
assert.Equal(t, expectedResponse, response)
}