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

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

@@ -0,0 +1,12 @@
### Structure
A Status node is a container of services.
These services are passed to geth and registered with geth as APIs and Protocols.
Status node manages all the services and the geth node.
Status node is managed by `api/geth_backend.go`
So:
`GethBackend` manages `StatusNode`, `StatusNode` manages `GethNode`

View File

@@ -0,0 +1,707 @@
package node
import (
"database/sql"
"errors"
"fmt"
"net"
"os"
"path/filepath"
"reflect"
"sync"
ma "github.com/multiformats/go-multiaddr"
"github.com/syndtr/goleveldb/leveldb"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/status-im/status-go/account"
"github.com/status-im/status-go/common"
"github.com/status-im/status-go/connection"
"github.com/status-im/status-go/db"
"github.com/status-im/status-go/discovery"
"github.com/status-im/status-go/ipfs"
"github.com/status-im/status-go/multiaccounts"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/peers"
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/server"
accountssvc "github.com/status-im/status-go/services/accounts"
appmetricsservice "github.com/status-im/status-go/services/appmetrics"
"github.com/status-im/status-go/services/browsers"
"github.com/status-im/status-go/services/chat"
"github.com/status-im/status-go/services/communitytokens"
"github.com/status-im/status-go/services/ens"
"github.com/status-im/status-go/services/gif"
localnotifications "github.com/status-im/status-go/services/local-notifications"
"github.com/status-im/status-go/services/mailservers"
"github.com/status-im/status-go/services/peer"
"github.com/status-im/status-go/services/permissions"
"github.com/status-im/status-go/services/personal"
"github.com/status-im/status-go/services/rpcfilters"
"github.com/status-im/status-go/services/rpcstats"
"github.com/status-im/status-go/services/status"
"github.com/status-im/status-go/services/stickers"
"github.com/status-im/status-go/services/subscriptions"
"github.com/status-im/status-go/services/updates"
"github.com/status-im/status-go/services/wakuext"
"github.com/status-im/status-go/services/wakuv2ext"
"github.com/status-im/status-go/services/wallet"
"github.com/status-im/status-go/services/web3provider"
"github.com/status-im/status-go/timesource"
"github.com/status-im/status-go/transactions"
"github.com/status-im/status-go/waku"
"github.com/status-im/status-go/wakuv2"
)
// errors
var (
ErrNodeRunning = errors.New("node is already running")
ErrNoGethNode = errors.New("geth node is not available")
ErrNoRunningNode = errors.New("there is no running node")
ErrAccountKeyStoreMissing = errors.New("account key store is not set")
ErrServiceUnknown = errors.New("service unknown")
ErrDiscoveryRunning = errors.New("discovery is already running")
ErrRPCMethodUnavailable = `{"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"the method called does not exist/is not available"}}`
)
// StatusNode abstracts contained geth node and provides helper methods to
// interact with it.
type StatusNode struct {
mu sync.RWMutex
appDB *sql.DB
multiaccountsDB *multiaccounts.Database
walletDB *sql.DB
config *params.NodeConfig // Status node configuration
gethNode *node.Node // reference to Geth P2P stack/node
rpcClient *rpc.Client // reference to an RPC client
downloader *ipfs.Downloader
httpServer *server.MediaServer
discovery discovery.Discovery
register *peers.Register
peerPool *peers.PeerPool
db *leveldb.DB // used as a cache for PeerPool
log log.Logger
gethAccountManager *account.GethManager
accountsManager *accounts.Manager
transactor *transactions.Transactor
// services
services []common.StatusService
publicMethods map[string]bool
// we explicitly list every service, we could use interfaces
// and store them in a nicer way and user reflection, but for now stupid is good
rpcFiltersSrvc *rpcfilters.Service
subscriptionsSrvc *subscriptions.Service
rpcStatsSrvc *rpcstats.Service
statusPublicSrvc *status.Service
accountsSrvc *accountssvc.Service
browsersSrvc *browsers.Service
permissionsSrvc *permissions.Service
mailserversSrvc *mailservers.Service
providerSrvc *web3provider.Service
appMetricsSrvc *appmetricsservice.Service
walletSrvc *wallet.Service
peerSrvc *peer.Service
localNotificationsSrvc *localnotifications.Service
personalSrvc *personal.Service
timeSourceSrvc *timesource.NTPTimeSource
wakuSrvc *waku.Waku
wakuExtSrvc *wakuext.Service
wakuV2Srvc *wakuv2.Waku
wakuV2ExtSrvc *wakuv2ext.Service
ensSrvc *ens.Service
communityTokensSrvc *communitytokens.Service
gifSrvc *gif.Service
stickersSrvc *stickers.Service
chatSrvc *chat.Service
updatesSrvc *updates.Service
pendingTracker *transactions.PendingTxTracker
walletFeed event.Feed
}
// New makes new instance of StatusNode.
func New(transactor *transactions.Transactor) *StatusNode {
return &StatusNode{
gethAccountManager: account.NewGethManager(),
transactor: transactor,
log: log.New("package", "status-go/node.StatusNode"),
publicMethods: make(map[string]bool),
}
}
// Config exposes reference to running node's configuration
func (n *StatusNode) Config() *params.NodeConfig {
n.mu.RLock()
defer n.mu.RUnlock()
return n.config
}
// GethNode returns underlying geth node.
func (n *StatusNode) GethNode() *node.Node {
n.mu.RLock()
defer n.mu.RUnlock()
return n.gethNode
}
func (n *StatusNode) HTTPServer() *server.MediaServer {
n.mu.RLock()
defer n.mu.RUnlock()
return n.httpServer
}
// Server retrieves the currently running P2P network layer.
func (n *StatusNode) Server() *p2p.Server {
n.mu.RLock()
defer n.mu.RUnlock()
if n.gethNode == nil {
return nil
}
return n.gethNode.Server()
}
// Start starts current StatusNode, failing if it's already started.
// It accepts a list of services that should be added to the node.
func (n *StatusNode) Start(config *params.NodeConfig, accs *accounts.Manager) error {
return n.StartWithOptions(config, StartOptions{
StartDiscovery: true,
AccountsManager: accs,
})
}
// StartOptions allows to control some parameters of Start() method.
type StartOptions struct {
StartDiscovery bool
AccountsManager *accounts.Manager
}
// StartMediaServerWithoutDB starts media server without starting the node
// The server can only handle requests that don't require appdb or IPFS downloader
func (n *StatusNode) StartMediaServerWithoutDB() error {
if n.isRunning() {
n.log.Debug("node is already running, no need to StartMediaServerWithoutDB")
return nil
}
if n.httpServer != nil {
if err := n.httpServer.Stop(); err != nil {
return err
}
}
httpServer, err := server.NewMediaServer(nil, nil, n.multiaccountsDB, nil)
if err != nil {
return err
}
n.httpServer = httpServer
if err := n.httpServer.Start(); err != nil {
return err
}
return nil
}
// StartWithOptions starts current StatusNode, failing if it's already started.
// It takes some options that allows to further configure starting process.
func (n *StatusNode) StartWithOptions(config *params.NodeConfig, options StartOptions) error {
n.mu.Lock()
defer n.mu.Unlock()
if n.isRunning() {
n.log.Debug("node is already running")
return ErrNodeRunning
}
n.accountsManager = options.AccountsManager
n.log.Debug("starting with options", "ClusterConfig", config.ClusterConfig)
db, err := db.Create(config.DataDir, params.StatusDatabase)
if err != nil {
return fmt.Errorf("failed to create database at %s: %v", config.DataDir, err)
}
n.db = db
err = n.startWithDB(config, options.AccountsManager, db)
// continue only if there was no error when starting node with a db
if err == nil && options.StartDiscovery && n.discoveryEnabled() {
err = n.startDiscovery()
}
if err != nil {
if dberr := db.Close(); dberr != nil {
n.log.Error("error while closing leveldb after node crash", "error", dberr)
}
n.db = nil
return err
}
return nil
}
func (n *StatusNode) startWithDB(config *params.NodeConfig, accs *accounts.Manager, db *leveldb.DB) error {
if err := n.createNode(config, accs, db); err != nil {
return err
}
n.config = config
if err := n.setupRPCClient(); err != nil {
return err
}
n.downloader = ipfs.NewDownloader(config.RootDataDir)
if n.httpServer != nil {
if err := n.httpServer.Stop(); err != nil {
return err
}
}
httpServer, err := server.NewMediaServer(n.appDB, n.downloader, n.multiaccountsDB, n.walletDB)
if err != nil {
return err
}
n.httpServer = httpServer
if err := n.httpServer.Start(); err != nil {
return err
}
if err := n.initServices(config, n.httpServer); err != nil {
return err
}
return n.startGethNode()
}
func (n *StatusNode) createNode(config *params.NodeConfig, accs *accounts.Manager, db *leveldb.DB) (err error) {
n.gethNode, err = MakeNode(config, accs, db)
return err
}
// startGethNode starts current StatusNode, will fail if it's already started.
func (n *StatusNode) startGethNode() error {
return n.gethNode.Start()
}
func (n *StatusNode) setupRPCClient() (err error) {
// setup RPC client
gethNodeClient, err := n.gethNode.Attach()
if err != nil {
return
}
n.rpcClient, err = rpc.NewClient(gethNodeClient, n.config.NetworkID, n.config.UpstreamConfig, n.config.Networks, n.appDB)
if err != nil {
return
}
return
}
func (n *StatusNode) discoveryEnabled() bool {
return n.config != nil && (!n.config.NoDiscovery || n.config.Rendezvous) && n.config.ClusterConfig.Enabled
}
func (n *StatusNode) discoverNode() (*enode.Node, error) {
if !n.isRunning() {
return nil, nil
}
server := n.gethNode.Server()
discNode := server.Self()
if n.config.AdvertiseAddr == "" {
return discNode, nil
}
n.log.Info("Using AdvertiseAddr for rendezvous", "addr", n.config.AdvertiseAddr)
r := discNode.Record()
r.Set(enr.IP(net.ParseIP(n.config.AdvertiseAddr)))
if err := enode.SignV4(r, server.PrivateKey); err != nil {
return nil, err
}
return enode.New(enode.ValidSchemes[r.IdentityScheme()], r)
}
func (n *StatusNode) startRendezvous() (discovery.Discovery, error) {
if !n.config.Rendezvous {
return nil, errors.New("rendezvous is not enabled")
}
if len(n.config.ClusterConfig.RendezvousNodes) == 0 {
return nil, errors.New("rendezvous node must be provided if rendezvous discovery is enabled")
}
maddrs := make([]ma.Multiaddr, len(n.config.ClusterConfig.RendezvousNodes))
for i, addr := range n.config.ClusterConfig.RendezvousNodes {
var err error
maddrs[i], err = ma.NewMultiaddr(addr)
if err != nil {
return nil, fmt.Errorf("failed to parse rendezvous node %s: %v", n.config.ClusterConfig.RendezvousNodes[0], err)
}
}
node, err := n.discoverNode()
if err != nil {
return nil, fmt.Errorf("failed to get a discover node: %v", err)
}
return discovery.NewRendezvous(maddrs, n.gethNode.Server().PrivateKey, node)
}
// StartDiscovery starts the peers discovery protocols depending on the node config.
func (n *StatusNode) StartDiscovery() error {
n.mu.Lock()
defer n.mu.Unlock()
if n.discoveryEnabled() {
return n.startDiscovery()
}
return nil
}
func (n *StatusNode) startDiscovery() error {
if n.isDiscoveryRunning() {
return ErrDiscoveryRunning
}
discoveries := []discovery.Discovery{}
if !n.config.NoDiscovery {
discoveries = append(discoveries, discovery.NewDiscV5(
n.gethNode.Server().PrivateKey,
n.config.ListenAddr,
parseNodesV5(n.config.ClusterConfig.BootNodes)))
}
if n.config.Rendezvous {
d, err := n.startRendezvous()
if err != nil {
return err
}
discoveries = append(discoveries, d)
}
if len(discoveries) == 0 {
return errors.New("wasn't able to register any discovery")
} else if len(discoveries) > 1 {
n.discovery = discovery.NewMultiplexer(discoveries)
} else {
n.discovery = discoveries[0]
}
log.Debug(
"using discovery",
"instance", reflect.TypeOf(n.discovery),
"registerTopics", n.config.RegisterTopics,
"requireTopics", n.config.RequireTopics,
)
n.register = peers.NewRegister(n.discovery, n.config.RegisterTopics...)
options := peers.NewDefaultOptions()
// TODO(dshulyak) consider adding a flag to define this behaviour
options.AllowStop = len(n.config.RegisterTopics) == 0
options.TrustedMailServers = parseNodesToNodeID(n.config.ClusterConfig.TrustedMailServers)
n.peerPool = peers.NewPeerPool(
n.discovery,
n.config.RequireTopics,
peers.NewCache(n.db),
options,
)
if err := n.discovery.Start(); err != nil {
return err
}
if err := n.register.Start(); err != nil {
return err
}
return n.peerPool.Start(n.gethNode.Server())
}
// Stop will stop current StatusNode. A stopped node cannot be resumed.
func (n *StatusNode) Stop() error {
n.mu.Lock()
defer n.mu.Unlock()
if !n.isRunning() {
return ErrNoRunningNode
}
return n.stop()
}
// stop will stop current StatusNode. A stopped node cannot be resumed.
func (n *StatusNode) stop() error {
if n.isDiscoveryRunning() {
if err := n.stopDiscovery(); err != nil {
n.log.Error("Error stopping the discovery components", "error", err)
}
n.register = nil
n.peerPool = nil
n.discovery = nil
}
if err := n.gethNode.Close(); err != nil {
return err
}
n.rpcClient = nil
// We need to clear `gethNode` because config is passed to `Start()`
// and may be completely different. Similarly with `config`.
n.gethNode = nil
n.config = nil
err := n.httpServer.Stop()
if err != nil {
return err
}
n.httpServer = nil
n.downloader.Stop()
n.downloader = nil
if n.db != nil {
err := n.db.Close()
n.db = nil
return err
}
n.rpcFiltersSrvc = nil
n.subscriptionsSrvc = nil
n.rpcStatsSrvc = nil
n.accountsSrvc = nil
n.browsersSrvc = nil
n.permissionsSrvc = nil
n.mailserversSrvc = nil
n.providerSrvc = nil
n.appMetricsSrvc = nil
n.walletSrvc = nil
n.peerSrvc = nil
n.localNotificationsSrvc = nil
n.personalSrvc = nil
n.timeSourceSrvc = nil
n.wakuSrvc = nil
n.wakuExtSrvc = nil
n.wakuV2Srvc = nil
n.wakuV2ExtSrvc = nil
n.ensSrvc = nil
n.communityTokensSrvc = nil
n.stickersSrvc = nil
n.publicMethods = make(map[string]bool)
n.pendingTracker = nil
n.log.Debug("status node stopped")
return nil
}
func (n *StatusNode) isDiscoveryRunning() bool {
return n.register != nil || n.peerPool != nil || n.discovery != nil
}
func (n *StatusNode) stopDiscovery() error {
n.register.Stop()
n.peerPool.Stop()
return n.discovery.Stop()
}
// ResetChainData removes chain data if node is not running.
func (n *StatusNode) ResetChainData(config *params.NodeConfig) error {
n.mu.Lock()
defer n.mu.Unlock()
if n.isRunning() {
return ErrNodeRunning
}
chainDataDir := filepath.Join(config.DataDir, config.Name, "lightchaindata")
if _, err := os.Stat(chainDataDir); os.IsNotExist(err) {
return err
}
err := os.RemoveAll(chainDataDir)
if err == nil {
n.log.Info("Chain data has been removed", "dir", chainDataDir)
}
return err
}
// IsRunning confirm that node is running.
func (n *StatusNode) IsRunning() bool {
n.mu.RLock()
defer n.mu.RUnlock()
return n.isRunning()
}
func (n *StatusNode) isRunning() bool {
return n.gethNode != nil && n.gethNode.Server() != nil
}
// populateStaticPeers connects current node with our publicly available LES/SHH/Swarm cluster
func (n *StatusNode) populateStaticPeers() error {
if !n.config.ClusterConfig.Enabled {
n.log.Info("Static peers are disabled")
return nil
}
for _, enode := range n.config.ClusterConfig.StaticNodes {
if err := n.addPeer(enode); err != nil {
n.log.Error("Static peer addition failed", "error", err)
return err
}
n.log.Info("Static peer added", "enode", enode)
}
return nil
}
func (n *StatusNode) removeStaticPeers() error {
if !n.config.ClusterConfig.Enabled {
n.log.Info("Static peers are disabled")
return nil
}
for _, enode := range n.config.ClusterConfig.StaticNodes {
if err := n.removePeer(enode); err != nil {
n.log.Error("Static peer deletion failed", "error", err)
return err
}
n.log.Info("Static peer deleted", "enode", enode)
}
return nil
}
// ReconnectStaticPeers removes and adds static peers to a server.
func (n *StatusNode) ReconnectStaticPeers() error {
n.mu.Lock()
defer n.mu.Unlock()
if !n.isRunning() {
return ErrNoRunningNode
}
if err := n.removeStaticPeers(); err != nil {
return err
}
return n.populateStaticPeers()
}
// AddPeer adds new static peer node
func (n *StatusNode) AddPeer(url string) error {
n.mu.RLock()
defer n.mu.RUnlock()
return n.addPeer(url)
}
// addPeer adds new static peer node
func (n *StatusNode) addPeer(url string) error {
parsedNode, err := enode.ParseV4(url)
if err != nil {
return err
}
if !n.isRunning() {
return ErrNoRunningNode
}
n.gethNode.Server().AddPeer(parsedNode)
return nil
}
func (n *StatusNode) removePeer(url string) error {
parsedNode, err := enode.ParseV4(url)
if err != nil {
return err
}
if !n.isRunning() {
return ErrNoRunningNode
}
n.gethNode.Server().RemovePeer(parsedNode)
return nil
}
// PeerCount returns the number of connected peers.
func (n *StatusNode) PeerCount() int {
n.mu.RLock()
defer n.mu.RUnlock()
if !n.isRunning() {
return 0
}
return n.gethNode.Server().PeerCount()
}
func (n *StatusNode) ConnectionChanged(state connection.State) {
if n.wakuExtSrvc != nil {
n.wakuExtSrvc.ConnectionChanged(state)
}
if n.wakuV2ExtSrvc != nil {
n.wakuV2ExtSrvc.ConnectionChanged(state)
}
}
// AccountManager exposes reference to node's accounts manager
func (n *StatusNode) AccountManager() (*accounts.Manager, error) {
n.mu.RLock()
defer n.mu.RUnlock()
if n.gethNode == nil {
return nil, ErrNoGethNode
}
return n.gethNode.AccountManager(), nil
}
// RPCClient exposes reference to RPC client connected to the running node.
func (n *StatusNode) RPCClient() *rpc.Client {
n.mu.RLock()
defer n.mu.RUnlock()
return n.rpcClient
}
// Discover sets up the discovery for a specific topic.
func (n *StatusNode) Discover(topic string, max, min int) (err error) {
if n.peerPool == nil {
return errors.New("peerPool not running")
}
return n.peerPool.UpdateTopic(topic, params.Limits{
Max: max,
Min: min,
})
}
func (n *StatusNode) SetAppDB(db *sql.DB) {
n.appDB = db
}
func (n *StatusNode) SetMultiaccountsDB(db *multiaccounts.Database) {
n.multiaccountsDB = db
}
func (n *StatusNode) SetWalletDB(db *sql.DB) {
n.walletDB = db
}

169
vendor/github.com/status-im/status-go/node/geth_node.go generated vendored Normal file
View File

@@ -0,0 +1,169 @@
package node
import (
"errors"
"fmt"
"os"
"path/filepath"
"github.com/syndtr/goleveldb/leveldb"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discv5"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/nat"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/params"
)
// Errors related to node and services creation.
var (
ErrNodeMakeFailureFormat = "error creating p2p node: %s"
ErrWakuServiceRegistrationFailure = errors.New("failed to register the Waku service")
ErrWakuV2ServiceRegistrationFailure = errors.New("failed to register the WakuV2 service")
ErrLightEthRegistrationFailure = errors.New("failed to register the LES service")
ErrLightEthRegistrationFailureUpstreamEnabled = errors.New("failed to register the LES service, upstream is also configured")
ErrPersonalServiceRegistrationFailure = errors.New("failed to register the personal api service")
ErrStatusServiceRegistrationFailure = errors.New("failed to register the Status service")
ErrPeerServiceRegistrationFailure = errors.New("failed to register the Peer service")
)
// All general log messages in this package should be routed through this logger.
var logger = log.New("package", "status-go/node")
// MakeNode creates a geth node entity
func MakeNode(config *params.NodeConfig, accs *accounts.Manager, db *leveldb.DB) (*node.Node, error) {
// If DataDir is empty, it means we want to create an ephemeral node
// keeping data only in memory.
if config.DataDir != "" {
// make sure data directory exists
if err := os.MkdirAll(filepath.Clean(config.DataDir), os.ModePerm); err != nil {
return nil, fmt.Errorf("make node: make data directory: %v", err)
}
// make sure keys directory exists
if err := os.MkdirAll(filepath.Clean(config.KeyStoreDir), os.ModePerm); err != nil {
return nil, fmt.Errorf("make node: make keys directory: %v", err)
}
}
stackConfig, err := newGethNodeConfig(config)
if err != nil {
return nil, err
}
stack, err := node.New(stackConfig)
if err != nil {
return nil, fmt.Errorf(ErrNodeMakeFailureFormat, err.Error())
}
return stack, nil
}
// newGethNodeConfig returns default stack configuration for mobile client node
func newGethNodeConfig(config *params.NodeConfig) (*node.Config, error) {
// NOTE: I haven't changed anything related to this parameters, but
// it seems they were previously ignored if set to 0, but now they seem
// to be used, so they need to be set to something
maxPeers := 100
maxPendingPeers := 100
if config.MaxPeers != 0 {
maxPeers = config.MaxPeers
}
if config.MaxPendingPeers != 0 {
maxPendingPeers = config.MaxPendingPeers
}
nc := &node.Config{
DataDir: config.DataDir,
KeyStoreDir: config.KeyStoreDir,
UseLightweightKDF: true,
NoUSB: true,
Name: config.Name,
Version: config.Version,
P2P: p2p.Config{
NoDiscovery: true, // we always use only v5 server
ListenAddr: config.ListenAddr,
NAT: nat.Any(),
MaxPeers: maxPeers,
MaxPendingPeers: maxPendingPeers,
},
HTTPModules: config.FormatAPIModules(),
}
if config.IPCEnabled {
// use well-known defaults
if config.IPCFile == "" {
config.IPCFile = "geth.ipc"
}
nc.IPCPath = config.IPCFile
}
if config.HTTPEnabled {
nc.HTTPHost = config.HTTPHost
nc.HTTPPort = config.HTTPPort
nc.HTTPVirtualHosts = config.HTTPVirtualHosts
nc.HTTPCors = config.HTTPCors
}
if config.ClusterConfig.Enabled {
nc.P2P.BootstrapNodesV5 = parseNodesV5(config.ClusterConfig.BootNodes)
nc.P2P.StaticNodes = parseNodes(config.ClusterConfig.StaticNodes)
}
if config.NodeKey != "" {
sk, err := crypto.HexToECDSA(config.NodeKey)
if err != nil {
return nil, err
}
// override node's private key
nc.P2P.PrivateKey = sk
}
return nc, nil
}
// parseNodes creates list of enode.Node out of enode strings.
func parseNodes(enodes []string) []*enode.Node {
var nodes []*enode.Node
for _, item := range enodes {
parsedPeer, err := enode.ParseV4(item)
if err == nil {
nodes = append(nodes, parsedPeer)
} else {
logger.Error("Failed to parse enode", "enode", item, "err", err)
}
}
return nodes
}
// parseNodesV5 creates list of discv5.Node out of enode strings.
func parseNodesV5(enodes []string) []*discv5.Node {
var nodes []*discv5.Node
for _, enode := range enodes {
parsedPeer, err := discv5.ParseNode(enode)
if err == nil {
nodes = append(nodes, parsedPeer)
} else {
logger.Error("Failed to parse enode", "enode", enode, "err", err)
}
}
return nodes
}
func parseNodesToNodeID(enodes []string) []enode.ID {
nodeIDs := make([]enode.ID, 0, len(enodes))
for _, node := range parseNodes(enodes) {
nodeIDs = append(nodeIDs, node.ID())
}
return nodeIDs
}

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

@@ -0,0 +1,30 @@
package node
import (
"reflect"
"unicode"
)
// firstCharToLower converts to first character of name to lowercase.
func firstCharToLower(name string) string {
ret := []rune(name)
if len(ret) > 0 {
ret[0] = unicode.ToLower(ret[0])
}
return string(ret)
}
// addSuitableCallbacks iterates over the methods of the given type and adds them to
// the methods list
// This is taken from go-ethereum services
func addSuitableCallbacks(receiver reflect.Value, namespace string, methods map[string]bool) {
typ := receiver.Type()
for m := 0; m < typ.NumMethod(); m++ {
method := typ.Method(m)
if method.PkgPath != "" {
continue // method not exported
}
name := firstCharToLower(method.Name)
methods[namespace+"_"+name] = true
}
}

View File

@@ -0,0 +1,711 @@
package node
import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"reflect"
"time"
"github.com/status-im/status-go/protocol/common/shard"
"github.com/status-im/status-go/server"
"github.com/status-im/status-go/signal"
"github.com/status-im/status-go/transactions"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/p2p/enode"
gethrpc "github.com/ethereum/go-ethereum/rpc"
"github.com/status-im/status-go/appmetrics"
"github.com/status-im/status-go/common"
gethbridge "github.com/status-im/status-go/eth-node/bridge/geth"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/logutils"
"github.com/status-im/status-go/mailserver"
"github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/multiaccounts/settings"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc"
accountssvc "github.com/status-im/status-go/services/accounts"
"github.com/status-im/status-go/services/accounts/settingsevent"
appmetricsservice "github.com/status-im/status-go/services/appmetrics"
"github.com/status-im/status-go/services/browsers"
"github.com/status-im/status-go/services/chat"
"github.com/status-im/status-go/services/communitytokens"
"github.com/status-im/status-go/services/ens"
"github.com/status-im/status-go/services/ext"
"github.com/status-im/status-go/services/gif"
localnotifications "github.com/status-im/status-go/services/local-notifications"
"github.com/status-im/status-go/services/mailservers"
"github.com/status-im/status-go/services/peer"
"github.com/status-im/status-go/services/permissions"
"github.com/status-im/status-go/services/personal"
"github.com/status-im/status-go/services/rpcfilters"
"github.com/status-im/status-go/services/rpcstats"
"github.com/status-im/status-go/services/status"
"github.com/status-im/status-go/services/stickers"
"github.com/status-im/status-go/services/subscriptions"
"github.com/status-im/status-go/services/updates"
"github.com/status-im/status-go/services/wakuext"
"github.com/status-im/status-go/services/wakuv2ext"
"github.com/status-im/status-go/services/wallet"
"github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/status-im/status-go/services/wallet/transfer"
"github.com/status-im/status-go/services/web3provider"
"github.com/status-im/status-go/timesource"
"github.com/status-im/status-go/waku"
wakucommon "github.com/status-im/status-go/waku/common"
"github.com/status-im/status-go/wakuv2"
)
var (
// ErrWakuClearIdentitiesFailure clearing whisper identities has failed.
ErrWakuClearIdentitiesFailure = errors.New("failed to clear waku identities")
// ErrRPCClientUnavailable is returned if an RPC client can't be retrieved.
// This is a normal situation when a node is stopped.
ErrRPCClientUnavailable = errors.New("JSON-RPC client is unavailable")
)
func (b *StatusNode) initServices(config *params.NodeConfig, mediaServer *server.MediaServer) error {
accountsFeed := &event.Feed{}
settingsFeed := &event.Feed{}
accDB, err := accounts.NewDB(b.appDB)
if err != nil {
return err
}
setSettingsNotifier(accDB, settingsFeed)
services := []common.StatusService{}
services = appendIf(config.UpstreamConfig.Enabled, services, b.rpcFiltersService())
services = append(services, b.subscriptionService())
services = append(services, b.rpcStatsService())
services = append(services, b.appmetricsService())
services = append(services, b.peerService())
services = append(services, b.personalService())
services = append(services, b.statusPublicService())
services = append(services, b.pendingTrackerService(&b.walletFeed))
services = append(services, b.ensService(b.timeSourceNow()))
services = append(services, b.CommunityTokensService())
services = append(services, b.stickersService(accDB))
services = append(services, b.updatesService())
services = appendIf(b.appDB != nil && b.multiaccountsDB != nil, services, b.accountsService(accountsFeed, accDB, mediaServer))
services = appendIf(config.BrowsersConfig.Enabled, services, b.browsersService())
services = appendIf(config.PermissionsConfig.Enabled, services, b.permissionsService())
services = appendIf(config.MailserversConfig.Enabled, services, b.mailserversService())
services = appendIf(config.Web3ProviderConfig.Enabled, services, b.providerService(accDB))
services = append(services, b.gifService(accDB))
services = append(services, b.ChatService(accDB))
// Wallet Service is used by wakuExtSrvc/wakuV2ExtSrvc
// Keep this initialization before the other two
if config.WalletConfig.Enabled {
walletService := b.walletService(accDB, b.appDB, accountsFeed, settingsFeed, &b.walletFeed)
services = append(services, walletService)
}
// CollectiblesManager needs the WakuExt service to get metadata for
// Community collectibles.
// Messenger needs the CollectiblesManager to get the list of collectibles owned
// by a certain account and check community entry permissions.
// We handle circular dependency between the two by delaying ininitalization of the CommunityCollectibleInfoProvider
// in the CollectiblesManager.
if config.WakuConfig.Enabled {
wakuService, err := b.wakuService(&config.WakuConfig, &config.ClusterConfig)
if err != nil {
return err
}
services = append(services, wakuService)
wakuext, err := b.wakuExtService(config)
if err != nil {
return err
}
b.wakuExtSrvc = wakuext
services = append(services, wakuext)
b.SetWalletCommunityInfoProvider(wakuext)
}
if config.WakuV2Config.Enabled {
telemetryServerURL := ""
if accDB.DB() != nil {
telemetryServerURL, err = accDB.GetTelemetryServerURL()
if err != nil {
return err
}
}
waku2Service, err := b.wakuV2Service(config, telemetryServerURL)
if err != nil {
return err
}
services = append(services, waku2Service)
wakuext, err := b.wakuV2ExtService(config)
if err != nil {
return err
}
b.wakuV2ExtSrvc = wakuext
services = append(services, wakuext)
b.SetWalletCommunityInfoProvider(wakuext)
}
// We ignore for now local notifications flag as users who are upgrading have no mean to enable it
lns, err := b.localNotificationsService(config.NetworkID)
if err != nil {
return err
}
services = append(services, lns)
b.peerSrvc.SetDiscoverer(b)
for i := range services {
b.RegisterLifecycle(services[i])
}
b.services = services
return nil
}
func (b *StatusNode) RegisterLifecycle(s common.StatusService) {
b.addPublicMethods(s.APIs())
b.gethNode.RegisterAPIs(s.APIs())
b.gethNode.RegisterProtocols(s.Protocols())
b.gethNode.RegisterLifecycle(s)
}
// Add through reflection a list of public methods so we can check when the
// user makes a call if they are allowed
func (b *StatusNode) addPublicMethods(apis []gethrpc.API) {
for _, api := range apis {
if api.Public {
addSuitableCallbacks(reflect.ValueOf(api.Service), api.Namespace, b.publicMethods)
}
}
}
func (b *StatusNode) nodeBridge() types.Node {
return gethbridge.NewNodeBridge(b.gethNode, b.wakuSrvc, b.wakuV2Srvc)
}
func (b *StatusNode) wakuExtService(config *params.NodeConfig) (*wakuext.Service, error) {
if b.gethNode == nil {
return nil, errors.New("geth node not initialized")
}
if b.wakuExtSrvc == nil {
b.wakuExtSrvc = wakuext.New(*config, b.nodeBridge(), b.rpcClient, ext.EnvelopeSignalHandler{}, b.db)
}
b.wakuExtSrvc.SetP2PServer(b.gethNode.Server())
return b.wakuExtSrvc, nil
}
func (b *StatusNode) wakuV2ExtService(config *params.NodeConfig) (*wakuv2ext.Service, error) {
if b.gethNode == nil {
return nil, errors.New("geth node not initialized")
}
if b.wakuV2ExtSrvc == nil {
b.wakuV2ExtSrvc = wakuv2ext.New(*config, b.nodeBridge(), b.rpcClient, ext.EnvelopeSignalHandler{}, b.db)
}
b.wakuV2ExtSrvc.SetP2PServer(b.gethNode.Server())
return b.wakuV2ExtSrvc, nil
}
func (b *StatusNode) statusPublicService() *status.Service {
if b.statusPublicSrvc == nil {
b.statusPublicSrvc = status.New()
}
return b.statusPublicSrvc
}
func (b *StatusNode) StatusPublicService() *status.Service {
return b.statusPublicSrvc
}
func (b *StatusNode) AccountService() *accountssvc.Service {
return b.accountsSrvc
}
func (b *StatusNode) BrowserService() *browsers.Service {
return b.browsersSrvc
}
func (b *StatusNode) EnsService() *ens.Service {
return b.ensSrvc
}
func (b *StatusNode) WakuService() *waku.Waku {
return b.wakuSrvc
}
func (b *StatusNode) WakuExtService() *wakuext.Service {
return b.wakuExtSrvc
}
func (b *StatusNode) WakuV2ExtService() *wakuv2ext.Service {
return b.wakuV2ExtSrvc
}
func (b *StatusNode) WakuV2Service() *wakuv2.Waku {
return b.wakuV2Srvc
}
func (b *StatusNode) wakuService(wakuCfg *params.WakuConfig, clusterCfg *params.ClusterConfig) (*waku.Waku, error) {
if b.wakuSrvc == nil {
cfg := &waku.Config{
MaxMessageSize: wakucommon.DefaultMaxMessageSize,
BloomFilterMode: wakuCfg.BloomFilterMode,
FullNode: wakuCfg.FullNode,
SoftBlacklistedPeerIDs: wakuCfg.SoftBlacklistedPeerIDs,
MinimumAcceptedPoW: params.WakuMinimumPoW,
EnableConfirmations: wakuCfg.EnableConfirmations,
}
if wakuCfg.MaxMessageSize > 0 {
cfg.MaxMessageSize = wakuCfg.MaxMessageSize
}
if wakuCfg.MinimumPoW > 0 {
cfg.MinimumAcceptedPoW = wakuCfg.MinimumPoW
}
w := waku.New(cfg, logutils.ZapLogger())
if wakuCfg.EnableRateLimiter {
r := wakuRateLimiter(wakuCfg, clusterCfg)
w.RegisterRateLimiter(r)
}
if timesource := b.timeSource(); timesource != nil {
w.SetTimeSource(timesource.Now)
}
// enable mail service
if wakuCfg.EnableMailServer {
if err := registerWakuMailServer(w, wakuCfg); err != nil {
return nil, fmt.Errorf("failed to register WakuMailServer: %v", err)
}
}
if wakuCfg.LightClient {
emptyBloomFilter := make([]byte, 64)
if err := w.SetBloomFilter(emptyBloomFilter); err != nil {
return nil, err
}
}
b.wakuSrvc = w
}
return b.wakuSrvc, nil
}
func (b *StatusNode) wakuV2Service(nodeConfig *params.NodeConfig, telemetryServerURL string) (*wakuv2.Waku, error) {
if b.wakuV2Srvc == nil {
cfg := &wakuv2.Config{
MaxMessageSize: wakucommon.DefaultMaxMessageSize,
Host: nodeConfig.WakuV2Config.Host,
Port: nodeConfig.WakuV2Config.Port,
LightClient: nodeConfig.WakuV2Config.LightClient,
KeepAliveInterval: nodeConfig.WakuV2Config.KeepAliveInterval,
Rendezvous: nodeConfig.Rendezvous,
WakuNodes: nodeConfig.ClusterConfig.WakuNodes,
PeerExchange: nodeConfig.WakuV2Config.PeerExchange,
EnableStore: nodeConfig.WakuV2Config.EnableStore,
StoreCapacity: nodeConfig.WakuV2Config.StoreCapacity,
StoreSeconds: nodeConfig.WakuV2Config.StoreSeconds,
DiscoveryLimit: nodeConfig.WakuV2Config.DiscoveryLimit,
DiscV5BootstrapNodes: nodeConfig.ClusterConfig.DiscV5BootstrapNodes,
Nameserver: nodeConfig.WakuV2Config.Nameserver,
EnableDiscV5: nodeConfig.WakuV2Config.EnableDiscV5,
UDPPort: nodeConfig.WakuV2Config.UDPPort,
AutoUpdate: nodeConfig.WakuV2Config.AutoUpdate,
DefaultShardPubsubTopic: shard.DefaultShardPubsubTopic(),
UseShardAsDefaultTopic: nodeConfig.WakuV2Config.UseShardAsDefaultTopic,
TelemetryServerURL: telemetryServerURL,
ClusterID: nodeConfig.ClusterConfig.ClusterID,
}
if nodeConfig.WakuV2Config.MaxMessageSize > 0 {
cfg.MaxMessageSize = nodeConfig.WakuV2Config.MaxMessageSize
}
w, err := wakuv2.New(nodeConfig.NodeKey, nodeConfig.ClusterConfig.Fleet, cfg, logutils.ZapLogger(), b.appDB, b.timeSource(), signal.SendHistoricMessagesRequestFailed, signal.SendPeerStats)
if err != nil {
return nil, err
}
b.wakuV2Srvc = w
}
return b.wakuV2Srvc, nil
}
func setSettingsNotifier(db *accounts.Database, feed *event.Feed) {
db.SetSettingsNotifier(func(setting settings.SettingField, val interface{}) {
feed.Send(settingsevent.Event{
Type: settingsevent.EventTypeChanged,
Setting: setting,
Value: val,
})
})
}
func wakuRateLimiter(wakuCfg *params.WakuConfig, clusterCfg *params.ClusterConfig) *wakucommon.PeerRateLimiter {
enodes := append(
parseNodes(clusterCfg.StaticNodes),
parseNodes(clusterCfg.TrustedMailServers)...,
)
var (
ips []string
peerIDs []enode.ID
)
for _, item := range enodes {
ips = append(ips, item.IP().String())
peerIDs = append(peerIDs, item.ID())
}
return wakucommon.NewPeerRateLimiter(
&wakucommon.PeerRateLimiterConfig{
PacketLimitPerSecIP: wakuCfg.PacketRateLimitIP,
PacketLimitPerSecPeerID: wakuCfg.PacketRateLimitPeerID,
BytesLimitPerSecIP: wakuCfg.BytesRateLimitIP,
BytesLimitPerSecPeerID: wakuCfg.BytesRateLimitPeerID,
WhitelistedIPs: ips,
WhitelistedPeerIDs: peerIDs,
},
&wakucommon.MetricsRateLimiterHandler{},
&wakucommon.DropPeerRateLimiterHandler{
Tolerance: wakuCfg.RateLimitTolerance,
},
)
}
func (b *StatusNode) rpcFiltersService() *rpcfilters.Service {
if b.rpcFiltersSrvc == nil {
b.rpcFiltersSrvc = rpcfilters.New(b)
}
return b.rpcFiltersSrvc
}
func (b *StatusNode) subscriptionService() *subscriptions.Service {
if b.subscriptionsSrvc == nil {
b.subscriptionsSrvc = subscriptions.New(func() *rpc.Client { return b.RPCClient() })
}
return b.subscriptionsSrvc
}
func (b *StatusNode) rpcStatsService() *rpcstats.Service {
if b.rpcStatsSrvc == nil {
b.rpcStatsSrvc = rpcstats.New()
}
return b.rpcStatsSrvc
}
func (b *StatusNode) accountsService(accountsFeed *event.Feed, accDB *accounts.Database, mediaServer *server.MediaServer) *accountssvc.Service {
if b.accountsSrvc == nil {
b.accountsSrvc = accountssvc.NewService(
accDB,
b.multiaccountsDB,
b.gethAccountManager,
b.config,
accountsFeed,
mediaServer,
)
}
return b.accountsSrvc
}
func (b *StatusNode) browsersService() *browsers.Service {
if b.browsersSrvc == nil {
b.browsersSrvc = browsers.NewService(browsers.NewDB(b.appDB))
}
return b.browsersSrvc
}
func (b *StatusNode) ensService(timesource func() time.Time) *ens.Service {
if b.ensSrvc == nil {
b.ensSrvc = ens.NewService(b.rpcClient, b.gethAccountManager, b.pendingTracker, b.config, b.appDB, timesource)
}
return b.ensSrvc
}
func (b *StatusNode) pendingTrackerService(walletFeed *event.Feed) *transactions.PendingTxTracker {
if b.pendingTracker == nil {
b.pendingTracker = transactions.NewPendingTxTracker(b.walletDB, b.rpcClient, b.rpcFiltersSrvc, walletFeed, transactions.PendingCheckInterval)
}
return b.pendingTracker
}
func (b *StatusNode) CommunityTokensService() *communitytokens.Service {
if b.communityTokensSrvc == nil {
b.communityTokensSrvc = communitytokens.NewService(b.rpcClient, b.gethAccountManager, b.pendingTracker, b.config, b.appDB)
}
return b.communityTokensSrvc
}
func (b *StatusNode) stickersService(accountDB *accounts.Database) *stickers.Service {
if b.stickersSrvc == nil {
b.stickersSrvc = stickers.NewService(accountDB, b.rpcClient, b.gethAccountManager, b.config, b.downloader, b.httpServer, b.pendingTracker)
}
return b.stickersSrvc
}
func (b *StatusNode) updatesService() *updates.Service {
if b.updatesSrvc == nil {
b.updatesSrvc = updates.NewService(b.ensService(b.timeSourceNow()))
}
return b.updatesSrvc
}
func (b *StatusNode) gifService(accountsDB *accounts.Database) *gif.Service {
if b.gifSrvc == nil {
b.gifSrvc = gif.NewService(accountsDB)
}
return b.gifSrvc
}
func (b *StatusNode) ChatService(accountsDB *accounts.Database) *chat.Service {
if b.chatSrvc == nil {
b.chatSrvc = chat.NewService(accountsDB)
}
return b.chatSrvc
}
func (b *StatusNode) permissionsService() *permissions.Service {
if b.permissionsSrvc == nil {
b.permissionsSrvc = permissions.NewService(permissions.NewDB(b.appDB))
}
return b.permissionsSrvc
}
func (b *StatusNode) mailserversService() *mailservers.Service {
if b.mailserversSrvc == nil {
b.mailserversSrvc = mailservers.NewService(mailservers.NewDB(b.appDB))
}
return b.mailserversSrvc
}
func (b *StatusNode) providerService(accountsDB *accounts.Database) *web3provider.Service {
web3S := web3provider.NewService(b.appDB, accountsDB, b.rpcClient, b.config, b.gethAccountManager, b.rpcFiltersSrvc, b.transactor)
if b.providerSrvc == nil {
b.providerSrvc = web3S
}
return b.providerSrvc
}
func (b *StatusNode) appmetricsService() common.StatusService {
if b.appMetricsSrvc == nil {
b.appMetricsSrvc = appmetricsservice.NewService(appmetrics.NewDB(b.appDB))
}
return b.appMetricsSrvc
}
func (b *StatusNode) WalletService() *wallet.Service {
return b.walletSrvc
}
func (b *StatusNode) SetWalletCommunityInfoProvider(provider thirdparty.CommunityInfoProvider) {
if b.walletSrvc != nil {
b.walletSrvc.SetWalletCommunityInfoProvider(provider)
}
}
func (b *StatusNode) walletService(accountsDB *accounts.Database, appDB *sql.DB, accountsFeed *event.Feed, settingsFeed *event.Feed, walletFeed *event.Feed) *wallet.Service {
if b.walletSrvc == nil {
b.walletSrvc = wallet.NewService(
b.walletDB, accountsDB, appDB, b.rpcClient, accountsFeed, settingsFeed, b.gethAccountManager, b.transactor, b.config,
b.ensService(b.timeSourceNow()),
b.stickersService(accountsDB),
b.pendingTracker,
walletFeed,
b.httpServer,
)
}
return b.walletSrvc
}
func (b *StatusNode) localNotificationsService(network uint64) (*localnotifications.Service, error) {
var err error
if b.localNotificationsSrvc == nil {
b.localNotificationsSrvc, err = localnotifications.NewService(b.appDB, transfer.NewDB(b.walletDB), network)
if err != nil {
return nil, err
}
}
return b.localNotificationsSrvc, nil
}
func (b *StatusNode) peerService() *peer.Service {
if b.peerSrvc == nil {
b.peerSrvc = peer.New()
}
return b.peerSrvc
}
func registerWakuMailServer(wakuService *waku.Waku, config *params.WakuConfig) (err error) {
var mailServer mailserver.WakuMailServer
wakuService.RegisterMailServer(&mailServer)
return mailServer.Init(wakuService, config)
}
func appendIf(condition bool, services []common.StatusService, service common.StatusService) []common.StatusService {
if !condition {
return services
}
return append(services, service)
}
func (b *StatusNode) RPCFiltersService() *rpcfilters.Service {
return b.rpcFiltersSrvc
}
func (b *StatusNode) PendingTracker() *transactions.PendingTxTracker {
return b.pendingTracker
}
func (b *StatusNode) StopLocalNotifications() error {
if b.localNotificationsSrvc == nil {
return nil
}
if b.localNotificationsSrvc.IsStarted() {
err := b.localNotificationsSrvc.Stop()
if err != nil {
b.log.Error("LocalNotifications service stop failed on StopLocalNotifications", "error", err)
return nil
}
}
return nil
}
func (b *StatusNode) StartLocalNotifications() error {
if b.localNotificationsSrvc == nil {
return nil
}
if b.walletSrvc == nil {
return nil
}
if !b.localNotificationsSrvc.IsStarted() {
err := b.localNotificationsSrvc.Start()
if err != nil {
b.log.Error("LocalNotifications service start failed on StartLocalNotifications", "error", err)
return nil
}
}
err := b.localNotificationsSrvc.SubscribeWallet(&b.walletFeed)
if err != nil {
b.log.Error("LocalNotifications service could not subscribe to wallet on StartLocalNotifications", "error", err)
return nil
}
return nil
}
// `personal_sign` and `personal_ecRecover` methods are important to
// keep DApps working.
// Usually, they are provided by an ETH or a LES service, but when using
// upstream, we don't start any of these, so we need to start our own
// implementation.
func (b *StatusNode) personalService() *personal.Service {
if b.personalSrvc == nil {
b.personalSrvc = personal.New(b.accountsManager)
}
return b.personalSrvc
}
func (b *StatusNode) timeSource() *timesource.NTPTimeSource {
if b.timeSourceSrvc == nil {
b.timeSourceSrvc = timesource.Default()
}
return b.timeSourceSrvc
}
func (b *StatusNode) timeSourceNow() func() time.Time {
return b.timeSource().Now
}
func (b *StatusNode) Cleanup() error {
if b.wakuSrvc != nil {
if err := b.wakuSrvc.DeleteKeyPairs(); err != nil {
return fmt.Errorf("%s: %v", ErrWakuClearIdentitiesFailure, err)
}
}
if b.Config() != nil && b.Config().WalletConfig.Enabled {
if b.walletSrvc != nil {
if b.walletSrvc.IsStarted() {
err := b.walletSrvc.Stop()
if err != nil {
return err
}
}
}
}
if b.ensSrvc != nil {
err := b.ensSrvc.Stop()
if err != nil {
return err
}
}
return nil
}
type RPCCall struct {
Method string `json:"method"`
}
func (b *StatusNode) CallPrivateRPC(inputJSON string) (string, error) {
b.mu.Lock()
defer b.mu.Unlock()
if b.rpcClient == nil {
return "", ErrRPCClientUnavailable
}
return b.rpcClient.CallRaw(inputJSON), nil
}
// CallRPC calls public methods on the node, we register public methods
// in a map and check if they can be called in this function
func (b *StatusNode) CallRPC(inputJSON string) (string, error) {
b.mu.Lock()
defer b.mu.Unlock()
if b.rpcClient == nil {
return "", ErrRPCClientUnavailable
}
rpcCall := &RPCCall{}
err := json.Unmarshal([]byte(inputJSON), rpcCall)
if err != nil {
return "", err
}
if rpcCall.Method == "" || !b.publicMethods[rpcCall.Method] {
return ErrRPCMethodUnavailable, nil
}
return b.rpcClient.CallRaw(inputJSON), nil
}