feat: Waku v2 bridge

Issue #12610
This commit is contained in:
Michal Iskierko
2023-11-12 13:29:38 +01:00
parent 56e7bd01ca
commit 6d31343205
6716 changed files with 1982502 additions and 5891 deletions

30
vendor/github.com/status-im/status-go/rpc/README.md generated vendored Normal file
View File

@@ -0,0 +1,30 @@
# rpc [![GoDoc](https://godoc.org/github.com/status-im/status-go/rpc?status.png)](https://godoc.org/github.com/status-im/status-go/rpc)
Package rpc - JSON-RPC client with custom routing.
Download:
```shell
go get github.com/status-im/status-go/rpc
```
* * *
Package rpc - JSON-RPC client with custom routing.
Package rpc implements status-go JSON-RPC client and handles
requests to different endpoints: upstream or local node.
Every JSON-RPC request coming from either JS code or any other part
of status-go should use this package to be handled and routed properly.
Routing rules are following:
- if Upstream is disabled, everything is routed to local ethereum-go node
- otherwise, some requests (from the list, see below) are routed to upstream, others - locally.
List of methods to be routed is currently available here: https://docs.google.com/spreadsheets/d/1N1nuzVN5tXoDmzkBLeC9_mwIlVH8DGF7YD2XwxA8BAE/edit#gid=0
Note, upon creation of a new client, it ok to be offline - client will keep trying to reconnect in background.
* * *
Automatically generated by [autoreadme](https://github.com/jimmyfrasche/autoreadme) on 2017.09.18

220
vendor/github.com/status-im/status-go/rpc/call_raw.go generated vendored Normal file
View File

@@ -0,0 +1,220 @@
package rpc
import (
"context"
"encoding/json"
gethrpc "github.com/ethereum/go-ethereum/rpc"
)
const (
jsonrpcVersion = "2.0"
errInvalidMessageCode = -32700 // from go-ethereum/rpc/errors.go
)
// for JSON-RPC responses obtained via CallRaw(), we have no way
// to know ID field from actual response. web3.js (primary and
// only user of CallRaw()) will validate response by checking
// ID field for being a number:
// https://github.com/ethereum/web3.js/blob/develop/lib/web3/jsonrpc.js#L66
// thus, we will use zero ID as a workaround of this limitation
var defaultMsgID = json.RawMessage(`0`)
// CallRaw performs a JSON-RPC call with already crafted JSON-RPC body. It
// returns string in JSON format with response (successul or error).
func (c *Client) CallRaw(body string) string {
ctx := context.Background()
return c.callRawContext(ctx, json.RawMessage(body))
}
// jsonrpcMessage represents JSON-RPC message
type jsonrpcMessage struct {
Version string `json:"jsonrpc"`
ID json.RawMessage `json:"id"`
}
type jsonrpcRequest struct {
jsonrpcMessage
ChainID uint64 `json:"chainId"`
Method string `json:"method"`
Params json.RawMessage `json:"params,omitempty"`
}
type jsonrpcSuccessfulResponse struct {
jsonrpcMessage
Result json.RawMessage `json:"result"`
}
type jsonrpcErrorResponse struct {
jsonrpcMessage
Error jsonError `json:"error"`
}
// jsonError represents Error message for JSON-RPC responses.
type jsonError struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}
// callRawContext performs a JSON-RPC call with already crafted JSON-RPC body and
// given context. It returns string in JSON format with response (successful or error).
//
// TODO(divan): this function exists for compatibility and uses default
// go-ethereum's RPC client under the hood. It adds some unnecessary overhead
// by first marshalling JSON string into object to use with normal Call,
// which is then umarshalled back to the same JSON. The same goes with response.
// This is waste of CPU and memory and should be avoided if possible,
// either by changing exported API (provide only Call, not CallRaw) or
// refactoring go-ethereum's client to allow using raw JSON directly.
func (c *Client) callRawContext(ctx context.Context, body json.RawMessage) string {
if isBatch(body) {
return c.callBatchMethods(ctx, body)
}
return c.callSingleMethod(ctx, body)
}
// callBatchMethods handles batched JSON-RPC requests, calling each of
// individual requests one by one and constructing proper batched response.
//
// See http://www.jsonrpc.org/specification#batch for details.
//
// We can't use gethtrpc.BatchCall here, because each call should go through
// our routing logic and router to corresponding destination.
func (c *Client) callBatchMethods(ctx context.Context, msgs json.RawMessage) string {
var requests []json.RawMessage
err := json.Unmarshal(msgs, &requests)
if err != nil {
return newErrorResponse(errInvalidMessageCode, err, defaultMsgID)
}
// run all methods sequentially, this seems to be main
// objective to use batched requests.
// See: https://github.com/ethereum/wiki/wiki/JavaScript-API#batch-requests
responses := make([]json.RawMessage, len(requests))
for i := range requests {
resp := c.callSingleMethod(ctx, requests[i])
responses[i] = json.RawMessage(resp)
}
data, err := json.Marshal(responses)
if err != nil {
c.log.Error("Failed to marshal batch responses:", "error", err)
return newErrorResponse(errInvalidMessageCode, err, defaultMsgID)
}
return string(data)
}
// callSingleMethod executes single JSON-RPC message and constructs proper response.
func (c *Client) callSingleMethod(ctx context.Context, msg json.RawMessage) string {
// unmarshal JSON body into json-rpc request
chainID, method, params, id, err := methodAndParamsFromBody(msg)
if err != nil {
return newErrorResponse(errInvalidMessageCode, err, id)
}
if chainID == 0 {
chainID = c.UpstreamChainID
}
// route and execute
var result json.RawMessage
err = c.CallContext(ctx, &result, chainID, method, params...)
// as we have to return original JSON, we have to
// analyze returned error and reconstruct original
// JSON error response.
if err != nil && err != gethrpc.ErrNoResult {
if er, ok := err.(gethrpc.Error); ok {
return newErrorResponse(er.ErrorCode(), err, id)
}
return newErrorResponse(errInvalidMessageCode, err, id)
}
// finally, marshal answer
return newSuccessResponse(result, id)
}
// methodAndParamsFromBody extracts Method and Params of
// JSON-RPC body into values ready to use with ethereum-go's
// RPC client Call() function. A lot of empty interface usage is
// due to the underlying code design :/
func methodAndParamsFromBody(body json.RawMessage) (uint64, string, []interface{}, json.RawMessage, error) {
msg, err := unmarshalMessage(body)
if err != nil {
return 0, "", nil, nil, err
}
params := []interface{}{}
if msg.Params != nil {
err = json.Unmarshal(msg.Params, &params)
if err != nil {
return 0, "", nil, nil, err
}
}
return msg.ChainID, msg.Method, params, msg.ID, nil
}
// unmarshalMessage tries to unmarshal JSON-RPC message.
func unmarshalMessage(body json.RawMessage) (*jsonrpcRequest, error) {
var msg jsonrpcRequest
err := json.Unmarshal(body, &msg)
return &msg, err
}
func newSuccessResponse(result json.RawMessage, id json.RawMessage) string {
if id == nil {
id = defaultMsgID
}
msg := &jsonrpcSuccessfulResponse{
jsonrpcMessage: jsonrpcMessage{
ID: id,
Version: jsonrpcVersion,
},
Result: result,
}
data, err := json.Marshal(msg)
if err != nil {
return newErrorResponse(errInvalidMessageCode, err, id)
}
return string(data)
}
func newErrorResponse(code int, err error, id json.RawMessage) string {
if id == nil {
id = defaultMsgID
}
errMsg := &jsonrpcErrorResponse{
jsonrpcMessage: jsonrpcMessage{
ID: id,
Version: jsonrpcVersion,
},
Error: jsonError{
Code: code,
Message: err.Error(),
},
}
data, _ := json.Marshal(errMsg)
return string(data)
}
// isBatch returns true when the first non-whitespace characters is '['
// code from go-ethereum's rpc client (rpc/client.go)
func isBatch(msg json.RawMessage) bool {
for _, c := range msg {
// skip insignificant whitespace (http://www.ietf.org/rfc/rfc4627.txt)
if c == 0x20 || c == 0x09 || c == 0x0a || c == 0x0d {
continue
}
return c == '['
}
return false
}

View File

@@ -0,0 +1,906 @@
package chain
import (
"context"
"fmt"
"math/big"
"strings"
"sync"
"time"
"github.com/afex/hystrix-go/hystrix"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rpc"
"github.com/status-im/status-go/services/rpcstats"
)
type FeeHistory struct {
BaseFeePerGas []string `json:"baseFeePerGas"`
}
type BatchCallClient interface {
BatchCallContext(ctx context.Context, b []rpc.BatchElem) error
}
type ClientInterface interface {
BatchCallContext(ctx context.Context, b []rpc.BatchElem) error
HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error)
BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error)
NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error)
FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error)
BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error)
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
FullTransactionByBlockNumberAndIndex(ctx context.Context, blockNumber *big.Int, index uint) (*FullTransaction, error)
GetBaseFeeFromBlock(blockNumber *big.Int) (string, error)
NetworkID() uint64
ToBigInt() *big.Int
CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error)
CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error
GetWalletNotifier() func(chainId uint64, message string)
SetWalletNotifier(notifier func(chainId uint64, message string))
TransactionByHash(ctx context.Context, hash common.Hash) (*types.Transaction, bool, error)
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
BlockNumber(ctx context.Context) (uint64, error)
SetIsConnected(value bool)
GetIsConnected() bool
bind.ContractCaller
bind.ContractTransactor
bind.ContractFilterer
}
type ClientWithFallback struct {
ChainID uint64
main *ethclient.Client
fallback *ethclient.Client
mainRPC *rpc.Client
fallbackRPC *rpc.Client
WalletNotifier func(chainId uint64, message string)
IsConnected bool
IsConnectedLock sync.RWMutex
LastCheckedAt int64
}
// Don't mark connection as failed if we get one of these errors
var propagateErrors = []error{
vm.ErrOutOfGas,
vm.ErrCodeStoreOutOfGas,
vm.ErrDepth,
vm.ErrInsufficientBalance,
vm.ErrContractAddressCollision,
vm.ErrExecutionReverted,
vm.ErrMaxCodeSizeExceeded,
vm.ErrInvalidJump,
vm.ErrWriteProtection,
vm.ErrReturnDataOutOfBounds,
vm.ErrGasUintOverflow,
vm.ErrInvalidCode,
vm.ErrNonceUintOverflow,
// Used by balance history to check state
ethereum.NotFound,
bind.ErrNoCode,
}
type CommandResult struct {
res1 any
res2 any
vmError error
}
func NewSimpleClient(main *rpc.Client, chainID uint64) *ClientWithFallback {
hystrix.ConfigureCommand(fmt.Sprintf("ethClient_%d", chainID), hystrix.CommandConfig{
Timeout: 10000,
MaxConcurrentRequests: 100,
SleepWindow: 300000,
ErrorPercentThreshold: 25,
})
return &ClientWithFallback{
ChainID: chainID,
main: ethclient.NewClient(main),
fallback: nil,
mainRPC: main,
fallbackRPC: nil,
IsConnected: true,
LastCheckedAt: time.Now().Unix(),
}
}
func NewClient(main, fallback *rpc.Client, chainID uint64) *ClientWithFallback {
hystrix.ConfigureCommand(fmt.Sprintf("ethClient_%d", chainID), hystrix.CommandConfig{
Timeout: 20000,
MaxConcurrentRequests: 100,
SleepWindow: 300000,
ErrorPercentThreshold: 25,
})
var fallbackEthClient *ethclient.Client
if fallback != nil {
fallbackEthClient = ethclient.NewClient(fallback)
}
return &ClientWithFallback{
ChainID: chainID,
main: ethclient.NewClient(main),
fallback: fallbackEthClient,
mainRPC: main,
fallbackRPC: fallback,
IsConnected: true,
LastCheckedAt: time.Now().Unix(),
}
}
func (c *ClientWithFallback) Close() {
c.main.Close()
if c.fallback != nil {
c.fallback.Close()
}
}
func isVMError(err error) bool {
if strings.HasPrefix(err.Error(), "execution reverted") {
return true
}
for _, vmError := range propagateErrors {
if err == vmError {
return true
}
}
return false
}
func (c *ClientWithFallback) SetIsConnected(value bool) {
c.IsConnectedLock.Lock()
defer c.IsConnectedLock.Unlock()
c.LastCheckedAt = time.Now().Unix()
if !value {
if c.IsConnected {
if c.WalletNotifier != nil {
c.WalletNotifier(c.ChainID, "down")
}
c.IsConnected = false
}
} else {
if !c.IsConnected {
c.IsConnected = true
if c.WalletNotifier != nil {
c.WalletNotifier(c.ChainID, "up")
}
}
}
}
func (c *ClientWithFallback) GetIsConnected() bool {
c.IsConnectedLock.RLock()
defer c.IsConnectedLock.RUnlock()
return c.IsConnected
}
func (c *ClientWithFallback) makeCallNoReturn(main func() error, fallback func() error) error {
resultChan := make(chan CommandResult, 1)
c.LastCheckedAt = time.Now().Unix()
errChan := hystrix.Go(fmt.Sprintf("ethClient_%d", c.ChainID), func() error {
err := main()
if err != nil {
if isVMError(err) {
resultChan <- CommandResult{vmError: err}
return nil
}
return err
}
c.SetIsConnected(true)
resultChan <- CommandResult{}
return nil
}, func(err error) error {
if c.fallback == nil {
c.SetIsConnected(false)
return err
}
err = fallback()
if err != nil {
if isVMError(err) {
resultChan <- CommandResult{vmError: err}
return nil
}
c.SetIsConnected(false)
return err
}
resultChan <- CommandResult{}
return nil
})
select {
case result := <-resultChan:
if result.vmError != nil {
return result.vmError
}
return nil
case err := <-errChan:
return err
}
}
func (c *ClientWithFallback) makeCallSingleReturn(main func() (any, error), fallback func() (any, error), toggleIsConnected bool) (any, error) {
resultChan := make(chan CommandResult, 1)
errChan := hystrix.Go(fmt.Sprintf("ethClient_%d", c.ChainID), func() error {
res, err := main()
if err != nil {
if isVMError(err) {
resultChan <- CommandResult{vmError: err}
return nil
}
return err
}
if toggleIsConnected {
c.SetIsConnected(true)
}
resultChan <- CommandResult{res1: res}
return nil
}, func(err error) error {
if c.fallback == nil {
if toggleIsConnected {
c.SetIsConnected(false)
}
return err
}
res, err := fallback()
if err != nil {
if isVMError(err) {
resultChan <- CommandResult{vmError: err}
return nil
}
if toggleIsConnected {
c.SetIsConnected(false)
}
return err
}
if toggleIsConnected {
c.SetIsConnected(true)
}
resultChan <- CommandResult{res1: res}
return nil
})
select {
case result := <-resultChan:
if result.vmError != nil {
return nil, result.vmError
}
return result.res1, nil
case err := <-errChan:
return nil, err
}
}
func (c *ClientWithFallback) makeCallDoubleReturn(main func() (any, any, error), fallback func() (any, any, error)) (any, any, error) {
resultChan := make(chan CommandResult, 1)
c.LastCheckedAt = time.Now().Unix()
errChan := hystrix.Go(fmt.Sprintf("ethClient_%d", c.ChainID), func() error {
a, b, err := main()
if err != nil {
if isVMError(err) {
resultChan <- CommandResult{vmError: err}
return nil
}
return err
}
c.SetIsConnected(true)
resultChan <- CommandResult{res1: a, res2: b}
return nil
}, func(err error) error {
if c.fallback == nil {
c.SetIsConnected(false)
return err
}
a, b, err := fallback()
if err != nil {
if isVMError(err) {
resultChan <- CommandResult{vmError: err}
return nil
}
c.SetIsConnected(false)
return err
}
c.SetIsConnected(true)
resultChan <- CommandResult{res1: a, res2: b}
return nil
})
select {
case result := <-resultChan:
if result.vmError != nil {
return nil, nil, result.vmError
}
return result.res1, result.res2, nil
case err := <-errChan:
return nil, nil, err
}
}
func (c *ClientWithFallback) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
rpcstats.CountCall("eth_BlockByHash")
block, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.BlockByHash(ctx, hash) },
func() (any, error) { return c.fallback.BlockByHash(ctx, hash) },
true,
)
if err != nil {
return nil, err
}
return block.(*types.Block), nil
}
func (c *ClientWithFallback) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) {
rpcstats.CountCall("eth_BlockByNumber")
block, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.BlockByNumber(ctx, number) },
func() (any, error) { return c.fallback.BlockByNumber(ctx, number) },
true,
)
if err != nil {
return nil, err
}
return block.(*types.Block), nil
}
func (c *ClientWithFallback) BlockNumber(ctx context.Context) (uint64, error) {
rpcstats.CountCall("eth_BlockNumber")
number, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.BlockNumber(ctx) },
func() (any, error) { return c.fallback.BlockNumber(ctx) },
true,
)
if err != nil {
return 0, err
}
return number.(uint64), nil
}
func (c *ClientWithFallback) PeerCount(ctx context.Context) (uint64, error) {
rpcstats.CountCall("eth_PeerCount")
peerCount, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.PeerCount(ctx) },
func() (any, error) { return c.fallback.PeerCount(ctx) },
true,
)
if err != nil {
return 0, err
}
return peerCount.(uint64), nil
}
func (c *ClientWithFallback) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
rpcstats.CountCall("eth_HeaderByHash")
header, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.HeaderByHash(ctx, hash) },
func() (any, error) { return c.fallback.HeaderByHash(ctx, hash) },
false,
)
if err != nil {
return nil, err
}
return header.(*types.Header), nil
}
func (c *ClientWithFallback) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
rpcstats.CountCall("eth_HeaderByNumber")
header, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.HeaderByNumber(ctx, number) },
func() (any, error) { return c.fallback.HeaderByNumber(ctx, number) },
false,
)
if err != nil {
return nil, err
}
return header.(*types.Header), nil
}
func (c *ClientWithFallback) TransactionByHash(ctx context.Context, hash common.Hash) (*types.Transaction, bool, error) {
rpcstats.CountCall("eth_TransactionByHash")
tx, isPending, err := c.makeCallDoubleReturn(
func() (any, any, error) { return c.main.TransactionByHash(ctx, hash) },
func() (any, any, error) { return c.fallback.TransactionByHash(ctx, hash) },
)
if err != nil {
return nil, false, err
}
return tx.(*types.Transaction), isPending.(bool), nil
}
func (c *ClientWithFallback) TransactionSender(ctx context.Context, tx *types.Transaction, block common.Hash, index uint) (common.Address, error) {
rpcstats.CountCall("eth_TransactionSender")
address, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.TransactionSender(ctx, tx, block, index) },
func() (any, error) { return c.fallback.TransactionSender(ctx, tx, block, index) },
true,
)
return address.(common.Address), err
}
func (c *ClientWithFallback) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) {
rpcstats.CountCall("eth_TransactionCount")
count, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.TransactionCount(ctx, blockHash) },
func() (any, error) { return c.fallback.TransactionCount(ctx, blockHash) },
true,
)
if err != nil {
return 0, err
}
return count.(uint), nil
}
func (c *ClientWithFallback) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) {
rpcstats.CountCall("eth_TransactionInBlock")
transactions, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.TransactionInBlock(ctx, blockHash, index) },
func() (any, error) { return c.fallback.TransactionInBlock(ctx, blockHash, index) },
true,
)
if err != nil {
return nil, err
}
return transactions.(*types.Transaction), nil
}
func (c *ClientWithFallback) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
rpcstats.CountCall("eth_TransactionReceipt")
receipt, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.TransactionReceipt(ctx, txHash) },
func() (any, error) { return c.fallback.TransactionReceipt(ctx, txHash) },
true,
)
if err != nil {
return nil, err
}
return receipt.(*types.Receipt), nil
}
func (c *ClientWithFallback) SyncProgress(ctx context.Context) (*ethereum.SyncProgress, error) {
rpcstats.CountCall("eth_SyncProgress")
progress, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.SyncProgress(ctx) },
func() (any, error) { return c.fallback.SyncProgress(ctx) },
true,
)
if err != nil {
return nil, err
}
return progress.(*ethereum.SyncProgress), nil
}
func (c *ClientWithFallback) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) {
rpcstats.CountCall("eth_SubscribeNewHead")
sub, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.SubscribeNewHead(ctx, ch) },
func() (any, error) { return c.fallback.SubscribeNewHead(ctx, ch) },
true,
)
if err != nil {
return nil, err
}
return sub.(ethereum.Subscription), nil
}
func (c *ClientWithFallback) NetworkID() uint64 {
return c.ChainID
}
func (c *ClientWithFallback) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) {
rpcstats.CountCall("eth_BalanceAt")
balance, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.BalanceAt(ctx, account, blockNumber) },
func() (any, error) { return c.fallback.BalanceAt(ctx, account, blockNumber) },
true,
)
if err != nil {
return nil, err
}
return balance.(*big.Int), nil
}
func (c *ClientWithFallback) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) {
rpcstats.CountCall("eth_StorageAt")
storage, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.StorageAt(ctx, account, key, blockNumber) },
func() (any, error) { return c.fallback.StorageAt(ctx, account, key, blockNumber) },
true,
)
if err != nil {
return nil, err
}
return storage.([]byte), nil
}
func (c *ClientWithFallback) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) {
rpcstats.CountCall("eth_CodeAt")
code, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.CodeAt(ctx, account, blockNumber) },
func() (any, error) { return c.fallback.CodeAt(ctx, account, blockNumber) },
true,
)
if err != nil {
return nil, err
}
return code.([]byte), nil
}
func (c *ClientWithFallback) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) {
rpcstats.CountCall("eth_NonceAt")
nonce, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.NonceAt(ctx, account, blockNumber) },
func() (any, error) { return c.fallback.NonceAt(ctx, account, blockNumber) },
true,
)
if err != nil {
return 0, err
}
return nonce.(uint64), nil
}
func (c *ClientWithFallback) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) {
rpcstats.CountCall("eth_FilterLogs")
logs, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.FilterLogs(ctx, q) },
func() (any, error) { return c.fallback.FilterLogs(ctx, q) },
true,
)
if err != nil {
return nil, err
}
return logs.([]types.Log), nil
}
func (c *ClientWithFallback) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) {
rpcstats.CountCall("eth_SubscribeFilterLogs")
sub, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.SubscribeFilterLogs(ctx, q, ch) },
func() (any, error) { return c.fallback.SubscribeFilterLogs(ctx, q, ch) },
true,
)
if err != nil {
return nil, err
}
return sub.(ethereum.Subscription), nil
}
func (c *ClientWithFallback) PendingBalanceAt(ctx context.Context, account common.Address) (*big.Int, error) {
rpcstats.CountCall("eth_PendingBalanceAt")
balance, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.PendingBalanceAt(ctx, account) },
func() (any, error) { return c.fallback.PendingBalanceAt(ctx, account) },
true,
)
if err != nil {
return nil, err
}
return balance.(*big.Int), nil
}
func (c *ClientWithFallback) PendingStorageAt(ctx context.Context, account common.Address, key common.Hash) ([]byte, error) {
rpcstats.CountCall("eth_PendingStorageAt")
storage, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.PendingStorageAt(ctx, account, key) },
func() (any, error) { return c.fallback.PendingStorageAt(ctx, account, key) },
true,
)
if err != nil {
return nil, err
}
return storage.([]byte), nil
}
func (c *ClientWithFallback) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) {
rpcstats.CountCall("eth_PendingCodeAt")
code, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.PendingCodeAt(ctx, account) },
func() (any, error) { return c.fallback.PendingCodeAt(ctx, account) },
true,
)
if err != nil {
return nil, err
}
return code.([]byte), nil
}
func (c *ClientWithFallback) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) {
rpcstats.CountCall("eth_PendingNonceAt")
nonce, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.PendingNonceAt(ctx, account) },
func() (any, error) { return c.fallback.PendingNonceAt(ctx, account) },
true,
)
if err != nil {
return 0, err
}
return nonce.(uint64), nil
}
func (c *ClientWithFallback) PendingTransactionCount(ctx context.Context) (uint, error) {
rpcstats.CountCall("eth_PendingTransactionCount")
count, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.PendingTransactionCount(ctx) },
func() (any, error) { return c.fallback.PendingTransactionCount(ctx) },
true,
)
if err != nil {
return 0, err
}
return count.(uint), nil
}
func (c *ClientWithFallback) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
rpcstats.CountCall("eth_CallContract_" + msg.To.String())
data, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.CallContract(ctx, msg, blockNumber) },
func() (any, error) { return c.fallback.CallContract(ctx, msg, blockNumber) },
true,
)
if err != nil {
return nil, err
}
return data.([]byte), nil
}
func (c *ClientWithFallback) CallContractAtHash(ctx context.Context, msg ethereum.CallMsg, blockHash common.Hash) ([]byte, error) {
rpcstats.CountCall("eth_CallContractAtHash")
data, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.CallContractAtHash(ctx, msg, blockHash) },
func() (any, error) { return c.fallback.CallContractAtHash(ctx, msg, blockHash) },
true,
)
if err != nil {
return nil, err
}
return data.([]byte), nil
}
func (c *ClientWithFallback) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) {
rpcstats.CountCall("eth_PendingCallContract")
data, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.PendingCallContract(ctx, msg) },
func() (any, error) { return c.fallback.PendingCallContract(ctx, msg) },
true,
)
if err != nil {
return nil, err
}
return data.([]byte), nil
}
func (c *ClientWithFallback) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
rpcstats.CountCall("eth_SuggestGasPrice")
gasPrice, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.SuggestGasPrice(ctx) },
func() (any, error) { return c.fallback.SuggestGasPrice(ctx) },
true,
)
if err != nil {
return nil, err
}
return gasPrice.(*big.Int), nil
}
func (c *ClientWithFallback) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
rpcstats.CountCall("eth_SuggestGasTipCap")
tip, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.SuggestGasTipCap(ctx) },
func() (any, error) { return c.fallback.SuggestGasTipCap(ctx) },
true,
)
if err != nil {
return nil, err
}
return tip.(*big.Int), nil
}
func (c *ClientWithFallback) FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (*ethereum.FeeHistory, error) {
rpcstats.CountCall("eth_FeeHistory")
feeHistory, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles) },
func() (any, error) { return c.fallback.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles) },
true,
)
if err != nil {
return nil, err
}
return feeHistory.(*ethereum.FeeHistory), nil
}
func (c *ClientWithFallback) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) {
rpcstats.CountCall("eth_EstimateGas")
estimate, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.EstimateGas(ctx, msg) },
func() (any, error) { return c.fallback.EstimateGas(ctx, msg) },
true,
)
if err != nil {
return 0, err
}
return estimate.(uint64), nil
}
func (c *ClientWithFallback) SendTransaction(ctx context.Context, tx *types.Transaction) error {
rpcstats.CountCall("eth_SendTransaction")
return c.makeCallNoReturn(
func() error { return c.main.SendTransaction(ctx, tx) },
func() error { return c.fallback.SendTransaction(ctx, tx) },
)
}
func (c *ClientWithFallback) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error {
rpcstats.CountCall("eth_CallContext")
return c.makeCallNoReturn(
func() error { return c.mainRPC.CallContext(ctx, result, method, args...) },
func() error { return c.fallbackRPC.CallContext(ctx, result, method, args...) },
)
}
func (c *ClientWithFallback) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error {
rpcstats.CountCall("eth_BatchCallContext")
return c.makeCallNoReturn(
func() error { return c.mainRPC.BatchCallContext(ctx, b) },
func() error { return c.fallbackRPC.BatchCallContext(ctx, b) },
)
}
func (c *ClientWithFallback) ToBigInt() *big.Int {
return big.NewInt(int64(c.ChainID))
}
func (c *ClientWithFallback) GetBaseFeeFromBlock(blockNumber *big.Int) (string, error) {
rpcstats.CountCall("eth_GetBaseFeeFromBlock")
var feeHistory FeeHistory
err := c.mainRPC.Call(&feeHistory, "eth_feeHistory", "0x1", (*hexutil.Big)(blockNumber), nil)
if err != nil {
if err.Error() == "the method eth_feeHistory does not exist/is not available" {
return "", nil
}
return "", err
}
var baseGasFee string = ""
if len(feeHistory.BaseFeePerGas) > 0 {
baseGasFee = feeHistory.BaseFeePerGas[0]
}
return baseGasFee, err
}
// go-ethereum's `Transaction` items drop the blkHash obtained during the RPC call.
// This function preserves the additional data. This is the cheapest way to obtain
// the block hash for a given block number.
func (c *ClientWithFallback) FullTransactionByBlockNumberAndIndex(ctx context.Context, blockNumber *big.Int, index uint) (*FullTransaction, error) {
rpcstats.CountCall("eth_FullTransactionByBlockNumberAndIndex")
tx, err := c.makeCallSingleReturn(
func() (any, error) {
return callFullTransactionByBlockNumberAndIndex(ctx, c.mainRPC, blockNumber, index)
},
func() (any, error) {
return callFullTransactionByBlockNumberAndIndex(ctx, c.fallbackRPC, blockNumber, index)
},
true,
)
if err != nil {
return nil, err
}
return tx.(*FullTransaction), nil
}
func (c *ClientWithFallback) GetWalletNotifier() func(chainId uint64, message string) {
return c.WalletNotifier
}
func (c *ClientWithFallback) SetWalletNotifier(notifier func(chainId uint64, message string)) {
c.WalletNotifier = notifier
}

108
vendor/github.com/status-im/status-go/rpc/chain/rpc.go generated vendored Normal file
View File

@@ -0,0 +1,108 @@
package chain
import (
"context"
"encoding/json"
"errors"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rpc"
)
// The code below is mostly copied from go-ethereum/ethclient (see TransactionInBlock), to keep the exact same behavior as the
// normal Transaction items, but exposing the additional data obtained in the `rpcTransaction` struct`.
// Unfortunately, the functions and classes used are not exposed outside of the package.
type FullTransaction struct {
Tx *types.Transaction
TxExtraInfo
}
type TxExtraInfo struct {
BlockNumber *hexutil.Big `json:"blockNumber,omitempty"`
BlockHash *common.Hash `json:"blockHash,omitempty"`
From *common.Address `json:"from,omitempty"`
}
func callFullTransactionByBlockNumberAndIndex(ctx context.Context, rpc *rpc.Client, number *big.Int, index uint) (*FullTransaction, error) {
var json *FullTransaction
err := rpc.CallContext(ctx, &json, "eth_getTransactionByBlockNumberAndIndex", toBlockNumArg(number), hexutil.Uint64(index))
if err != nil {
return nil, err
}
if json == nil {
return nil, ethereum.NotFound
} else if _, r, _ := json.Tx.RawSignatureValues(); r == nil {
return nil, fmt.Errorf("server returned transaction without signature")
}
if json.From != nil && json.BlockHash != nil {
setSenderFromServer(json.Tx, *json.From, *json.BlockHash)
}
return json, nil
}
func (tx *FullTransaction) UnmarshalJSON(msg []byte) error {
if err := json.Unmarshal(msg, &tx.Tx); err != nil {
return err
}
return json.Unmarshal(msg, &tx.TxExtraInfo)
}
func toBlockNumArg(number *big.Int) string {
if number == nil {
return "latest"
}
pending := big.NewInt(-1)
if number.Cmp(pending) == 0 {
return "pending"
}
finalized := big.NewInt(int64(rpc.FinalizedBlockNumber))
if number.Cmp(finalized) == 0 {
return "finalized"
}
safe := big.NewInt(int64(rpc.SafeBlockNumber))
if number.Cmp(safe) == 0 {
return "safe"
}
return hexutil.EncodeBig(number)
}
type senderFromServer struct {
addr common.Address
blockhash common.Hash
}
var errNotCached = errors.New("sender not cached")
//nolint:errcheck
func setSenderFromServer(tx *types.Transaction, addr common.Address, block common.Hash) {
// Use types.Sender for side-effect to store our signer into the cache.
types.Sender(&senderFromServer{addr, block}, tx)
}
func (s *senderFromServer) Equal(other types.Signer) bool {
os, ok := other.(*senderFromServer)
return ok && os.blockhash == s.blockhash
}
func (s *senderFromServer) Sender(tx *types.Transaction) (common.Address, error) {
if s.addr == (common.Address{}) {
return common.Address{}, errNotCached
}
return s.addr, nil
}
func (s *senderFromServer) ChainID() *big.Int {
panic("can't sign with senderFromServer")
}
func (s *senderFromServer) Hash(tx *types.Transaction) common.Hash {
panic("can't sign with senderFromServer")
}
func (s *senderFromServer) SignatureValues(tx *types.Transaction, sig []byte) (R, S, V *big.Int, err error) {
panic("can't sign with senderFromServer")
}

352
vendor/github.com/status-im/status-go/rpc/client.go generated vendored Normal file
View File

@@ -0,0 +1,352 @@
package rpc
import (
"context"
"database/sql"
"encoding/json"
"errors"
"fmt"
"reflect"
"sync"
"time"
"github.com/ethereum/go-ethereum/log"
gethrpc "github.com/ethereum/go-ethereum/rpc"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc/chain"
"github.com/status-im/status-go/rpc/network"
"github.com/status-im/status-go/services/rpcstats"
"github.com/status-im/status-go/services/wallet/common"
)
const (
// DefaultCallTimeout is a default timeout for an RPC call
DefaultCallTimeout = time.Minute
)
// List of RPC client errors.
var (
ErrMethodNotFound = fmt.Errorf("The method does not exist/is not available")
)
// Handler defines handler for RPC methods.
type Handler func(context.Context, uint64, ...interface{}) (interface{}, error)
type ClientInterface interface {
AbstractEthClient(chainID common.ChainID) (chain.BatchCallClient, error)
}
// Client represents RPC client with custom routing
// scheme. It automatically decides where RPC call
// goes - Upstream or Local node.
type Client struct {
sync.RWMutex
upstreamEnabled bool
upstreamURL string
UpstreamChainID uint64
local *gethrpc.Client
upstream chain.ClientInterface
rpcClientsMutex sync.RWMutex
rpcClients map[uint64]chain.ClientInterface
router *router
NetworkManager *network.Manager
handlersMx sync.RWMutex // mx guards handlers
handlers map[string]Handler // locally registered handlers
log log.Logger
walletNotifier func(chainID uint64, message string)
}
// Is initialized in a build-tag-dependent module
var verifProxyInitFn func(c *Client)
// NewClient initializes Client and tries to connect to both,
// upstream and local node.
//
// Client is safe for concurrent use and will automatically
// reconnect to the server if connection is lost.
func NewClient(client *gethrpc.Client, upstreamChainID uint64, upstream params.UpstreamRPCConfig, networks []params.Network, db *sql.DB) (*Client, error) {
var err error
log := log.New("package", "status-go/rpc.Client")
networkManager := network.NewManager(db)
err = networkManager.Init(networks)
if err != nil {
log.Error("Network manager failed to initialize", "error", err)
}
c := Client{
local: client,
NetworkManager: networkManager,
handlers: make(map[string]Handler),
rpcClients: make(map[uint64]chain.ClientInterface),
log: log,
}
if upstream.Enabled {
c.UpstreamChainID = upstreamChainID
c.upstreamEnabled = upstream.Enabled
c.upstreamURL = upstream.URL
upstreamClient, err := gethrpc.Dial(c.upstreamURL)
if err != nil {
return nil, fmt.Errorf("dial upstream server: %s", err)
}
c.upstream = chain.NewSimpleClient(upstreamClient, upstreamChainID)
}
c.router = newRouter(c.upstreamEnabled)
if verifProxyInitFn != nil {
verifProxyInitFn(&c)
}
return &c, nil
}
func (c *Client) SetWalletNotifier(notifier func(chainID uint64, message string)) {
c.walletNotifier = notifier
}
func (c *Client) getClientUsingCache(chainID uint64) (chain.ClientInterface, error) {
c.rpcClientsMutex.Lock()
defer c.rpcClientsMutex.Unlock()
if rpcClient, ok := c.rpcClients[chainID]; ok {
if rpcClient.GetWalletNotifier() == nil {
rpcClient.SetWalletNotifier(c.walletNotifier)
}
return rpcClient, nil
}
network := c.NetworkManager.Find(chainID)
if network == nil {
if c.UpstreamChainID == chainID {
return c.upstream, nil
}
return nil, fmt.Errorf("could not find network: %d", chainID)
}
rpcClient, err := gethrpc.Dial(network.RPCURL)
if err != nil {
return nil, fmt.Errorf("dial upstream server: %s", err)
}
var rpcFallbackClient *gethrpc.Client
if len(network.FallbackURL) > 0 {
rpcFallbackClient, err = gethrpc.Dial(network.FallbackURL)
if err != nil {
return nil, fmt.Errorf("dial upstream server: %s", err)
}
}
client := chain.NewClient(rpcClient, rpcFallbackClient, chainID)
client.WalletNotifier = c.walletNotifier
c.rpcClients[chainID] = client
return client, nil
}
// Ethclient returns ethclient.Client per chain
func (c *Client) EthClient(chainID uint64) (chain.ClientInterface, error) {
client, err := c.getClientUsingCache(chainID)
if err != nil {
return nil, err
}
return client, nil
}
// AbstractEthClient returns a partial abstraction used by new components for testing purposes
func (c *Client) AbstractEthClient(chainID common.ChainID) (chain.BatchCallClient, error) {
client, err := c.getClientUsingCache(uint64(chainID))
if err != nil {
return nil, err
}
return client, nil
}
func (c *Client) EthClients(chainIDs []uint64) (map[uint64]chain.ClientInterface, error) {
clients := make(map[uint64]chain.ClientInterface, 0)
for _, chainID := range chainIDs {
client, err := c.getClientUsingCache(chainID)
if err != nil {
return nil, err
}
clients[chainID] = client
}
return clients, nil
}
// SetClient strictly for testing purposes
func (c *Client) SetClient(chainID uint64, client chain.ClientInterface) {
c.rpcClientsMutex.Lock()
defer c.rpcClientsMutex.Unlock()
c.rpcClients[chainID] = client
}
// UpdateUpstreamURL changes the upstream RPC client URL, if the upstream is enabled.
func (c *Client) UpdateUpstreamURL(url string) error {
if c.upstream == nil {
return nil
}
rpcClient, err := gethrpc.Dial(url)
if err != nil {
return err
}
c.Lock()
c.upstream = chain.NewSimpleClient(rpcClient, c.UpstreamChainID)
c.upstreamURL = url
c.Unlock()
return nil
}
// Call performs a JSON-RPC call with the given arguments and unmarshals into
// result if no error occurred.
//
// The result must be a pointer so that package json can unmarshal into it. You
// can also pass nil, in which case the result is ignored.
//
// It uses custom routing scheme for calls.
func (c *Client) Call(result interface{}, chainID uint64, method string, args ...interface{}) error {
ctx := context.Background()
return c.CallContext(ctx, result, chainID, method, args...)
}
// CallContext performs a JSON-RPC call with the given arguments. If the context is
// canceled before the call has successfully returned, CallContext returns immediately.
//
// The result must be a pointer so that package json can unmarshal into it. You
// can also pass nil, in which case the result is ignored.
//
// It uses custom routing scheme for calls.
// If there are any local handlers registered for this call, they will handle it.
func (c *Client) CallContext(ctx context.Context, result interface{}, chainID uint64, method string, args ...interface{}) error {
rpcstats.CountCall(method)
if c.router.routeBlocked(method) {
return ErrMethodNotFound
}
// check locally registered handlers first
if handler, ok := c.handler(method); ok {
return c.callMethod(ctx, result, chainID, handler, args...)
}
return c.CallContextIgnoringLocalHandlers(ctx, result, chainID, method, args...)
}
// CallContextIgnoringLocalHandlers performs a JSON-RPC call with the given
// arguments.
//
// If there are local handlers registered for this call, they would
// be ignored. It is useful if the call is happening from within a local
// handler itself.
// Upstream calls routing will be used anyway.
func (c *Client) CallContextIgnoringLocalHandlers(ctx context.Context, result interface{}, chainID uint64, method string, args ...interface{}) error {
if c.router.routeBlocked(method) {
return ErrMethodNotFound
}
if c.router.routeRemote(method) {
client, err := c.getClientUsingCache(chainID)
if err != nil {
return err
}
return client.CallContext(ctx, result, method, args...)
}
if c.local == nil {
c.log.Warn("Local JSON-RPC endpoint missing", "method", method)
return errors.New("missing local JSON-RPC endpoint")
}
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
}
// UnregisterHandler removes a previously registered handler.
func (c *Client) UnregisterHandler(method string) {
c.handlersMx.Lock()
defer c.handlersMx.Unlock()
delete(c.handlers, method)
}
// 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{}, chainID uint64, handler Handler, args ...interface{}) error {
response, err := handler(ctx, chainID, 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
}
return setResultFromRPCResponse(result, response)
}
// 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
}

21
vendor/github.com/status-im/status-go/rpc/doc.go generated vendored Normal file
View File

@@ -0,0 +1,21 @@
/*
Package rpc - JSON-RPC client with custom routing.
Package rpc implements status-go JSON-RPC client and handles
requests to different endpoints: upstream or local node.
Every JSON-RPC request coming from either JS code or any other part
of status-go should use this package to be handled and routed properly.
Routing rules are following:
- if Upstream is disabled, everything is routed to local ethereum-go node
- otherwise, some requests (from the list, see below) are routed to upstream, others - locally.
List of methods to be routed is currently available here: https://docs.google.com/spreadsheets/d/1N1nuzVN5tXoDmzkBLeC9_mwIlVH8DGF7YD2XwxA8BAE/edit#gid=0
Note, upon creation of a new client, it ok to be offline - client will keep trying to reconnect in background.
*/
package rpc
//go:generate autoreadme -f

View File

@@ -0,0 +1,326 @@
package network
import (
"bytes"
"database/sql"
"fmt"
"github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/params"
)
var SepoliaChainIDs = []uint64{11155111, 421614, 11155420}
var GoerliChainIDs = []uint64{5, 421613, 420}
type CombinedNetwork struct {
Prod *params.Network
Test *params.Network
}
const baseQuery = "SELECT chain_id, chain_name, rpc_url, original_rpc_url, fallback_url, original_fallback_url, block_explorer_url, icon_url, native_currency_name, native_currency_symbol, native_currency_decimals, is_test, layer, enabled, chain_color, short_name, related_chain_id FROM networks"
func newNetworksQuery() *networksQuery {
buf := bytes.NewBuffer(nil)
buf.WriteString(baseQuery)
return &networksQuery{buf: buf}
}
type networksQuery struct {
buf *bytes.Buffer
args []interface{}
added bool
}
func (nq *networksQuery) andOrWhere() {
if nq.added {
nq.buf.WriteString(" AND")
} else {
nq.buf.WriteString(" WHERE")
}
}
func (nq *networksQuery) filterEnabled(enabled bool) *networksQuery {
nq.andOrWhere()
nq.added = true
nq.buf.WriteString(" enabled = ?")
nq.args = append(nq.args, enabled)
return nq
}
func (nq *networksQuery) filterChainID(chainID uint64) *networksQuery {
nq.andOrWhere()
nq.added = true
nq.buf.WriteString(" chain_id = ?")
nq.args = append(nq.args, chainID)
return nq
}
func (nq *networksQuery) exec(db *sql.DB) ([]*params.Network, error) {
rows, err := db.Query(nq.buf.String(), nq.args...)
if err != nil {
return nil, err
}
var res []*params.Network
defer rows.Close()
for rows.Next() {
network := params.Network{}
err := rows.Scan(
&network.ChainID, &network.ChainName, &network.RPCURL, &network.OriginalRPCURL, &network.FallbackURL, &network.OriginalFallbackURL,
&network.BlockExplorerURL, &network.IconURL, &network.NativeCurrencyName, &network.NativeCurrencySymbol,
&network.NativeCurrencyDecimals, &network.IsTest, &network.Layer, &network.Enabled, &network.ChainColor, &network.ShortName,
&network.RelatedChainID,
)
if err != nil {
return nil, err
}
res = append(res, &network)
}
return res, err
}
type Manager struct {
db *sql.DB
configuredNetworks []params.Network
accountsDB *accounts.Database
}
func NewManager(db *sql.DB) *Manager {
accountsDB, err := accounts.NewDB(db)
if err != nil {
return nil
}
return &Manager{
db: db,
accountsDB: accountsDB,
}
}
func find(chainID uint64, networks []params.Network) int {
for i := range networks {
if networks[i].ChainID == chainID {
return i
}
}
return -1
}
func (nm *Manager) Init(networks []params.Network) error {
if networks == nil {
return nil
}
nm.configuredNetworks = networks
var errors string
currentNetworks, _ := nm.Get(false)
// Delete networks which are not supported any more
for i := range currentNetworks {
if find(currentNetworks[i].ChainID, networks) == -1 {
err := nm.Delete(currentNetworks[i].ChainID)
if err != nil {
errors += fmt.Sprintf("error deleting network with ChainID: %d, %s", currentNetworks[i].ChainID, err.Error())
}
}
}
// Add new networks and update related chain id for the old ones
for i := range networks {
found := false
networks[i].OriginalRPCURL = networks[i].RPCURL
networks[i].OriginalFallbackURL = networks[i].FallbackURL
for j := range currentNetworks {
if currentNetworks[j].ChainID == networks[i].ChainID {
found = true
if currentNetworks[j].RelatedChainID != networks[i].RelatedChainID {
// Update fallback_url if it's different
err := nm.UpdateRelatedChainID(currentNetworks[j].ChainID, networks[i].RelatedChainID)
if err != nil {
errors += fmt.Sprintf("error updating network fallback_url for ChainID: %d, %s", currentNetworks[j].ChainID, err.Error())
}
}
if networks[i].OriginalRPCURL != currentNetworks[j].OriginalRPCURL && currentNetworks[j].RPCURL == currentNetworks[j].OriginalRPCURL {
err := nm.updateRPCURL(networks[i].ChainID, networks[i].OriginalRPCURL)
if err != nil {
errors += fmt.Sprintf("error updating rpc url for ChainID: %d, %s", currentNetworks[j].ChainID, err.Error())
}
}
if networks[i].OriginalFallbackURL != currentNetworks[j].OriginalFallbackURL && currentNetworks[j].FallbackURL == currentNetworks[j].OriginalFallbackURL {
err := nm.updateFallbackURL(networks[i].ChainID, networks[i].OriginalFallbackURL)
if err != nil {
errors += fmt.Sprintf("error updating rpc url for ChainID: %d, %s", currentNetworks[j].ChainID, err.Error())
}
}
err := nm.updateOriginalURLs(networks[i].ChainID, networks[i].OriginalRPCURL, networks[i].OriginalFallbackURL)
if err != nil {
errors += fmt.Sprintf("error updating network original url for ChainID: %d, %s", currentNetworks[j].ChainID, err.Error())
}
break
}
}
if !found {
// Insert new network
err := nm.Upsert(&networks[i])
if err != nil {
errors += fmt.Sprintf("error inserting network with ChainID: %d, %s", networks[i].ChainID, err.Error())
}
}
}
if len(errors) > 0 {
return fmt.Errorf(errors)
}
return nil
}
func (nm *Manager) Upsert(network *params.Network) error {
_, err := nm.db.Exec(
"INSERT OR REPLACE INTO networks (chain_id, chain_name, rpc_url, original_rpc_url, fallback_url, original_fallback_url, block_explorer_url, icon_url, native_currency_name, native_currency_symbol, native_currency_decimals, is_test, layer, enabled, chain_color, short_name, related_chain_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
network.ChainID, network.ChainName, network.RPCURL, network.OriginalRPCURL, network.FallbackURL, network.OriginalFallbackURL, network.BlockExplorerURL, network.IconURL,
network.NativeCurrencyName, network.NativeCurrencySymbol, network.NativeCurrencyDecimals,
network.IsTest, network.Layer, network.Enabled, network.ChainColor, network.ShortName,
network.RelatedChainID,
)
return err
}
func (nm *Manager) Delete(chainID uint64) error {
_, err := nm.db.Exec("DELETE FROM networks WHERE chain_id = ?", chainID)
return err
}
func (nm *Manager) UpdateRelatedChainID(chainID uint64, relatedChainID uint64) error {
_, err := nm.db.Exec(`UPDATE networks SET related_chain_id = ? WHERE chain_id = ?`, relatedChainID, chainID)
return err
}
func (nm *Manager) updateRPCURL(chainID uint64, rpcURL string) error {
_, err := nm.db.Exec(`UPDATE networks SET rpc_url = ? WHERE chain_id = ?`, rpcURL, chainID)
return err
}
func (nm *Manager) updateFallbackURL(chainID uint64, fallbackURL string) error {
_, err := nm.db.Exec(`UPDATE networks SET fallback_url = ? WHERE chain_id = ?`, fallbackURL, chainID)
return err
}
func (nm *Manager) updateOriginalURLs(chainID uint64, originalRPCURL, OriginalFallbackURL string) error {
_, err := nm.db.Exec(`UPDATE networks SET original_rpc_url = ?, original_fallback_url = ? WHERE chain_id = ?`, originalRPCURL, OriginalFallbackURL, chainID)
return err
}
func (nm *Manager) Find(chainID uint64) *params.Network {
networks, err := newNetworksQuery().filterChainID(chainID).exec(nm.db)
if len(networks) != 1 || err != nil {
return nil
}
return networks[0]
}
func (nm *Manager) GetAll() ([]*params.Network, error) {
query := newNetworksQuery()
return query.exec(nm.db)
}
func (nm *Manager) Get(onlyEnabled bool) ([]*params.Network, error) {
isSepoliaEnabled, err := nm.accountsDB.GetIsSepoliaEnabled()
if err != nil {
return nil, err
}
query := newNetworksQuery()
if onlyEnabled {
query.filterEnabled(true)
}
networks, err := query.exec(nm.db)
if err != nil {
return nil, err
}
var results []*params.Network
for _, network := range networks {
if !isSepoliaEnabled {
found := false
for _, chainID := range SepoliaChainIDs {
if network.ChainID == chainID {
found = true
break
}
}
if found {
continue
}
}
if isSepoliaEnabled {
found := false
for _, chainID := range GoerliChainIDs {
if network.ChainID == chainID {
found = true
break
}
}
if found {
continue
}
}
results = append(results, network)
}
return results, nil
}
func (nm *Manager) GetCombinedNetworks() ([]*CombinedNetwork, error) {
networks, err := nm.Get(false)
if err != nil {
return nil, err
}
var combinedNetworks []*CombinedNetwork
for _, network := range networks {
found := false
for _, n := range combinedNetworks {
if (n.Test != nil && (network.ChainID == n.Test.RelatedChainID || n.Test.ChainID == network.RelatedChainID)) || (n.Prod != nil && (network.ChainID == n.Prod.RelatedChainID || n.Prod.ChainID == network.RelatedChainID)) {
found = true
if network.IsTest {
n.Test = network
break
} else {
n.Prod = network
break
}
}
}
if found {
continue
}
newCombined := &CombinedNetwork{}
if network.IsTest {
newCombined.Test = network
} else {
newCombined.Prod = network
}
combinedNetworks = append(combinedNetworks, newCombined)
}
return combinedNetworks, nil
}
func (nm *Manager) GetConfiguredNetworks() []params.Network {
return nm.configuredNetworks
}
func (nm *Manager) GetTestNetworksEnabled() (result bool, err error) {
return nm.accountsDB.GetTestNetworksEnabled()
}

115
vendor/github.com/status-im/status-go/rpc/route.go generated vendored Normal file
View File

@@ -0,0 +1,115 @@
package rpc
// router implements logic for routing
// JSON-RPC requests either to Upstream or
// Local node.
type router struct {
methods map[string]bool
blockedMethods map[string]struct{}
upstreamEnabled bool
}
// newRouter inits new router.
func newRouter(upstreamEnabled bool) *router {
r := &router{
methods: make(map[string]bool),
blockedMethods: make(map[string]struct{}),
upstreamEnabled: upstreamEnabled,
}
for _, m := range remoteMethods {
r.methods[m] = true
}
for _, m := range blockedMethods {
r.blockedMethods[m] = struct{}{}
}
return r
}
// routeRemote returns true if given method should be routed to the remote node
func (r *router) routeRemote(method string) bool {
if !r.upstreamEnabled {
return false
}
// else check route using the methods list
return r.methods[method]
}
func (r *router) routeBlocked(method string) bool {
_, ok := r.blockedMethods[method]
return ok
}
// blockedMethods is a list of dangerous or having security implications JSON-RPC methods
// that are not allowed to be called.
var blockedMethods = [...]string{
"shh_getPrivateKey",
}
// BlockedMethods returns a list of methods that are not allowed to be called.
// A copy of a slice is returned in order to prevent from changing it from outside.
func BlockedMethods() []string {
return append([]string(nil), blockedMethods[:]...)
}
// remoteMethods contains methods that should be routed to
// the upstream node; the rest is considered to be routed to
// the local node.
// A list of supported methods:
//
// curl --include \
// --header "Content-Type: application/json" \
// --header "Accept: application/json" 'https://api.infura.io/v1/jsonrpc/ropsten/methods'
//
// 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.
// Also, we want new requests in newer Geth versions not to be accidentally routed to the upstream.
// The list of methods: https://github.com/ethereum/wiki/wiki/JSON-RPC
var remoteMethods = [...]string{
"eth_protocolVersion",
"eth_syncing",
"eth_coinbase",
"eth_mining",
"eth_hashrate",
"eth_gasPrice",
"eth_maxPriorityFeePerGas",
"eth_feeHistory",
//"eth_accounts", // due to sub-accounts handling
"eth_blockNumber",
"eth_getBalance",
"eth_getStorageAt",
"eth_getTransactionCount",
"eth_getBlockTransactionCountByHash",
"eth_getBlockTransactionCountByNumber",
"eth_getUncleCountByBlockHash",
"eth_getUncleCountByBlockNumber",
"eth_getCode",
//"eth_sign", // only the local node has an injected account to sign the payload with
//"eth_sendTransaction", // we handle this specially calling eth_estimateGas, signing it locally and sending eth_sendRawTransaction afterwards
"eth_sendRawTransaction",
"eth_call",
"eth_estimateGas",
"eth_getBlockByHash",
"eth_getBlockByNumber",
"eth_getTransactionByHash",
"eth_getTransactionByBlockHashAndIndex",
"eth_getTransactionByBlockNumberAndIndex",
"eth_getTransactionReceipt",
"eth_getUncleByBlockHashAndIndex",
"eth_getUncleByBlockNumberAndIndex",
//"eth_getCompilers", // goes to the local because there's no need to send it anywhere
//"eth_compileLLL", // goes to the local because there's no need to send it anywhere
//"eth_compileSolidity", // goes to the local because there's no need to send it anywhere
//"eth_compileSerpent", // goes to the local because there's no need to send it anywhere
"eth_getLogs",
"eth_getWork",
"eth_submitWork",
"eth_submitHashrate",
"net_version",
"net_peerCount",
"net_listening",
}

View File

@@ -0,0 +1,72 @@
//go:build nimbus_light_client
// +build nimbus_light_client
package rpc
import (
"context"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
gethrpc "github.com/ethereum/go-ethereum/rpc"
proxy "github.com/siphiuel/lc-proxy-wrapper"
"github.com/status-im/status-go/params"
)
type VerifProxy struct {
config *proxy.Config
client *gethrpc.Client
log log.Logger
}
func init() {
verifProxyInitFn = func(c *Client) {
ctx := context.Background()
var testConfig = proxy.Config{
Eth2Network: "mainnet",
TrustedBlockRoot: "0xc5182cdb750fe088138b0d475683cda26a96befc24de16fb17bcf49d9cadf2f7",
Web3Url: c.upstreamURL,
RpcAddress: "127.0.0.1",
RpcPort: 8545,
LogLevel: "INFO",
}
proxy.StartLightClient(ctx, &testConfig)
verifProxy, err := newVerifProxy(&testConfig, c.log)
if err != nil {
c.RegisterHandler(
params.BalanceMethodName,
func(ctx context.Context, v uint64, params ...interface{}) (interface{}, error) {
addr := params[0].(common.Address)
return verifProxy.GetBalance(ctx, addr)
},
)
}
}
}
func newVerifProxy(cfg *proxy.Config, log log.Logger) (*VerifProxy, error) {
endpoint := "http://" + cfg.RpcAddress + ":" + fmt.Sprint(cfg.RpcPort)
client, err := gethrpc.DialHTTP(endpoint)
if err != nil {
log.Error("Error when creating VerifProxy client", err)
return nil, err
}
proxy := &VerifProxy{cfg, client, log}
return proxy, nil
}
func (p *VerifProxy) GetBalance(ctx context.Context, address common.Address) (interface{}, error) {
var result hexutil.Big
err := p.client.CallContext(ctx, &result, "eth_getBalance", address, "latest")
if err != nil {
p.log.Error("Error when invoking GetBalance", err)
return nil, err
}
return result, nil
}