github.com/status-im/status-go@v1.1.0/node/get_status_node.go (about) 1 package node 2 3 import ( 4 "database/sql" 5 "errors" 6 "fmt" 7 "net" 8 "os" 9 "path/filepath" 10 "reflect" 11 "sync" 12 13 "github.com/syndtr/goleveldb/leveldb" 14 15 "github.com/ethereum/go-ethereum/accounts" 16 "github.com/ethereum/go-ethereum/event" 17 "github.com/ethereum/go-ethereum/log" 18 "github.com/ethereum/go-ethereum/node" 19 "github.com/ethereum/go-ethereum/p2p" 20 "github.com/ethereum/go-ethereum/p2p/enode" 21 "github.com/ethereum/go-ethereum/p2p/enr" 22 23 "github.com/status-im/status-go/account" 24 "github.com/status-im/status-go/common" 25 "github.com/status-im/status-go/connection" 26 "github.com/status-im/status-go/db" 27 "github.com/status-im/status-go/discovery" 28 "github.com/status-im/status-go/ipfs" 29 "github.com/status-im/status-go/multiaccounts" 30 "github.com/status-im/status-go/params" 31 "github.com/status-im/status-go/peers" 32 "github.com/status-im/status-go/rpc" 33 "github.com/status-im/status-go/server" 34 accountssvc "github.com/status-im/status-go/services/accounts" 35 appgeneral "github.com/status-im/status-go/services/app-general" 36 appmetricsservice "github.com/status-im/status-go/services/appmetrics" 37 "github.com/status-im/status-go/services/browsers" 38 "github.com/status-im/status-go/services/chat" 39 "github.com/status-im/status-go/services/communitytokens" 40 "github.com/status-im/status-go/services/connector" 41 "github.com/status-im/status-go/services/ens" 42 "github.com/status-im/status-go/services/gif" 43 localnotifications "github.com/status-im/status-go/services/local-notifications" 44 "github.com/status-im/status-go/services/mailservers" 45 "github.com/status-im/status-go/services/peer" 46 "github.com/status-im/status-go/services/permissions" 47 "github.com/status-im/status-go/services/personal" 48 "github.com/status-im/status-go/services/rpcfilters" 49 "github.com/status-im/status-go/services/rpcstats" 50 "github.com/status-im/status-go/services/status" 51 "github.com/status-im/status-go/services/stickers" 52 "github.com/status-im/status-go/services/subscriptions" 53 "github.com/status-im/status-go/services/updates" 54 "github.com/status-im/status-go/services/wakuext" 55 "github.com/status-im/status-go/services/wakuv2ext" 56 "github.com/status-im/status-go/services/wallet" 57 "github.com/status-im/status-go/services/web3provider" 58 "github.com/status-im/status-go/timesource" 59 "github.com/status-im/status-go/transactions" 60 "github.com/status-im/status-go/waku" 61 "github.com/status-im/status-go/wakuv2" 62 ) 63 64 // errors 65 var ( 66 ErrNodeRunning = errors.New("node is already running") 67 ErrNoGethNode = errors.New("geth node is not available") 68 ErrNoRunningNode = errors.New("there is no running node") 69 ErrAccountKeyStoreMissing = errors.New("account key store is not set") 70 ErrServiceUnknown = errors.New("service unknown") 71 ErrDiscoveryRunning = errors.New("discovery is already running") 72 ErrRPCMethodUnavailable = `{"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"the method called does not exist/is not available"}}` 73 ) 74 75 // StatusNode abstracts contained geth node and provides helper methods to 76 // interact with it. 77 type StatusNode struct { 78 mu sync.RWMutex 79 80 appDB *sql.DB 81 multiaccountsDB *multiaccounts.Database 82 walletDB *sql.DB 83 84 config *params.NodeConfig // Status node configuration 85 gethNode *node.Node // reference to Geth P2P stack/node 86 rpcClient *rpc.Client // reference to an RPC client 87 88 downloader *ipfs.Downloader 89 httpServer *server.MediaServer 90 91 discovery discovery.Discovery 92 register *peers.Register 93 peerPool *peers.PeerPool 94 db *leveldb.DB // used as a cache for PeerPool 95 96 log log.Logger 97 98 gethAccountManager *account.GethManager 99 accountsManager *accounts.Manager 100 transactor *transactions.Transactor 101 102 // services 103 services []common.StatusService 104 publicMethods map[string]bool 105 // we explicitly list every service, we could use interfaces 106 // and store them in a nicer way and user reflection, but for now stupid is good 107 rpcFiltersSrvc *rpcfilters.Service 108 subscriptionsSrvc *subscriptions.Service 109 rpcStatsSrvc *rpcstats.Service 110 statusPublicSrvc *status.Service 111 accountsSrvc *accountssvc.Service 112 browsersSrvc *browsers.Service 113 permissionsSrvc *permissions.Service 114 mailserversSrvc *mailservers.Service 115 providerSrvc *web3provider.Service 116 appMetricsSrvc *appmetricsservice.Service 117 walletSrvc *wallet.Service 118 peerSrvc *peer.Service 119 localNotificationsSrvc *localnotifications.Service 120 personalSrvc *personal.Service 121 timeSourceSrvc *timesource.NTPTimeSource 122 wakuSrvc *waku.Waku 123 wakuExtSrvc *wakuext.Service 124 wakuV2Srvc *wakuv2.Waku 125 wakuV2ExtSrvc *wakuv2ext.Service 126 ensSrvc *ens.Service 127 communityTokensSrvc *communitytokens.Service 128 gifSrvc *gif.Service 129 stickersSrvc *stickers.Service 130 chatSrvc *chat.Service 131 updatesSrvc *updates.Service 132 pendingTracker *transactions.PendingTxTracker 133 connectorSrvc *connector.Service 134 appGeneralSrvc *appgeneral.Service 135 136 accountsFeed event.Feed 137 walletFeed event.Feed 138 } 139 140 // New makes new instance of StatusNode. 141 func New(transactor *transactions.Transactor) *StatusNode { 142 return &StatusNode{ 143 gethAccountManager: account.NewGethManager(), 144 transactor: transactor, 145 log: log.New("package", "status-go/node.StatusNode"), 146 publicMethods: make(map[string]bool), 147 } 148 } 149 150 // Config exposes reference to running node's configuration 151 func (n *StatusNode) Config() *params.NodeConfig { 152 n.mu.RLock() 153 defer n.mu.RUnlock() 154 155 return n.config 156 } 157 158 // GethNode returns underlying geth node. 159 func (n *StatusNode) GethNode() *node.Node { 160 n.mu.RLock() 161 defer n.mu.RUnlock() 162 163 return n.gethNode 164 } 165 166 func (n *StatusNode) HTTPServer() *server.MediaServer { 167 n.mu.RLock() 168 defer n.mu.RUnlock() 169 170 return n.httpServer 171 } 172 173 // Server retrieves the currently running P2P network layer. 174 func (n *StatusNode) Server() *p2p.Server { 175 n.mu.RLock() 176 defer n.mu.RUnlock() 177 178 if n.gethNode == nil { 179 return nil 180 } 181 182 return n.gethNode.Server() 183 } 184 185 // Start starts current StatusNode, failing if it's already started. 186 // It accepts a list of services that should be added to the node. 187 func (n *StatusNode) Start(config *params.NodeConfig, accs *accounts.Manager) error { 188 return n.StartWithOptions(config, StartOptions{ 189 StartDiscovery: true, 190 AccountsManager: accs, 191 }) 192 } 193 194 // StartOptions allows to control some parameters of Start() method. 195 type StartOptions struct { 196 StartDiscovery bool 197 AccountsManager *accounts.Manager 198 } 199 200 // StartMediaServerWithoutDB starts media server without starting the node 201 // The server can only handle requests that don't require appdb or IPFS downloader 202 func (n *StatusNode) StartMediaServerWithoutDB() error { 203 if n.isRunning() { 204 n.log.Debug("node is already running, no need to StartMediaServerWithoutDB") 205 return nil 206 } 207 208 if n.httpServer != nil { 209 if err := n.httpServer.Stop(); err != nil { 210 return err 211 } 212 } 213 214 httpServer, err := server.NewMediaServer(nil, nil, n.multiaccountsDB, nil) 215 if err != nil { 216 return err 217 } 218 219 n.httpServer = httpServer 220 221 if err := n.httpServer.Start(); err != nil { 222 return err 223 } 224 225 return nil 226 } 227 228 // StartWithOptions starts current StatusNode, failing if it's already started. 229 // It takes some options that allows to further configure starting process. 230 func (n *StatusNode) StartWithOptions(config *params.NodeConfig, options StartOptions) error { 231 n.mu.Lock() 232 defer n.mu.Unlock() 233 234 if n.isRunning() { 235 n.log.Debug("node is already running") 236 return ErrNodeRunning 237 } 238 239 n.accountsManager = options.AccountsManager 240 241 n.log.Debug("starting with options", "ClusterConfig", config.ClusterConfig) 242 243 db, err := db.Create(config.DataDir, params.StatusDatabase) 244 if err != nil { 245 return fmt.Errorf("failed to create database at %s: %v", config.DataDir, err) 246 } 247 248 n.db = db 249 250 err = n.startWithDB(config, options.AccountsManager, db) 251 252 // continue only if there was no error when starting node with a db 253 if err == nil && options.StartDiscovery && n.discoveryEnabled() { 254 err = n.startDiscovery() 255 } 256 257 if err != nil { 258 if dberr := db.Close(); dberr != nil { 259 n.log.Error("error while closing leveldb after node crash", "error", dberr) 260 } 261 n.db = nil 262 return err 263 } 264 265 return nil 266 } 267 268 func (n *StatusNode) startWithDB(config *params.NodeConfig, accs *accounts.Manager, db *leveldb.DB) error { 269 if err := n.createNode(config, accs, db); err != nil { 270 return err 271 } 272 n.config = config 273 274 if err := n.setupRPCClient(); err != nil { 275 return err 276 } 277 278 n.downloader = ipfs.NewDownloader(config.RootDataDir) 279 280 if n.httpServer != nil { 281 if err := n.httpServer.Stop(); err != nil { 282 return err 283 } 284 } 285 286 httpServer, err := server.NewMediaServer(n.appDB, n.downloader, n.multiaccountsDB, n.walletDB) 287 if err != nil { 288 return err 289 } 290 291 n.httpServer = httpServer 292 293 if err := n.httpServer.Start(); err != nil { 294 return err 295 } 296 297 if err := n.initServices(config, n.httpServer); err != nil { 298 return err 299 } 300 return n.startGethNode() 301 } 302 303 func (n *StatusNode) createNode(config *params.NodeConfig, accs *accounts.Manager, db *leveldb.DB) (err error) { 304 n.gethNode, err = MakeNode(config, accs, db) 305 return err 306 } 307 308 // startGethNode starts current StatusNode, will fail if it's already started. 309 func (n *StatusNode) startGethNode() error { 310 return n.gethNode.Start() 311 } 312 313 func (n *StatusNode) setupRPCClient() (err error) { 314 // setup RPC client 315 gethNodeClient, err := n.gethNode.Attach() 316 if err != nil { 317 return 318 } 319 320 // ProviderConfigs should be passed not in wallet secrets config on login 321 // but some other way, as it's not wallet specific and should not be passed with login request 322 // but currently there is no other way to pass it 323 providerConfigs := []params.ProviderConfig{ 324 { 325 Enabled: n.config.WalletConfig.StatusProxyEnabled, 326 Name: rpc.ProviderStatusProxy, 327 User: n.config.WalletConfig.StatusProxyBlockchainUser, 328 Password: n.config.WalletConfig.StatusProxyBlockchainPassword, 329 }, 330 } 331 332 n.rpcClient, err = rpc.NewClient(gethNodeClient, n.config.NetworkID, n.config.UpstreamConfig, n.config.Networks, n.appDB, providerConfigs) 333 if err != nil { 334 return 335 } 336 337 return 338 } 339 340 func (n *StatusNode) discoveryEnabled() bool { 341 return n.config != nil && (!n.config.NoDiscovery) && n.config.ClusterConfig.Enabled 342 } 343 344 func (n *StatusNode) discoverNode() (*enode.Node, error) { 345 if !n.isRunning() { 346 return nil, nil 347 } 348 349 server := n.gethNode.Server() 350 discNode := server.Self() 351 352 if n.config.AdvertiseAddr == "" { 353 return discNode, nil 354 } 355 356 n.log.Info("Using AdvertiseAddr for rendezvous", "addr", n.config.AdvertiseAddr) 357 358 r := discNode.Record() 359 r.Set(enr.IP(net.ParseIP(n.config.AdvertiseAddr))) 360 if err := enode.SignV4(r, server.PrivateKey); err != nil { 361 return nil, err 362 } 363 return enode.New(enode.ValidSchemes[r.IdentityScheme()], r) 364 } 365 366 // StartDiscovery starts the peers discovery protocols depending on the node config. 367 func (n *StatusNode) StartDiscovery() error { 368 n.mu.Lock() 369 defer n.mu.Unlock() 370 371 if n.discoveryEnabled() { 372 return n.startDiscovery() 373 } 374 375 return nil 376 } 377 378 func (n *StatusNode) startDiscovery() error { 379 if n.isDiscoveryRunning() { 380 return ErrDiscoveryRunning 381 } 382 383 discoveries := []discovery.Discovery{} 384 if !n.config.NoDiscovery { 385 discoveries = append(discoveries, discovery.NewDiscV5( 386 n.gethNode.Server().PrivateKey, 387 n.config.ListenAddr, 388 parseNodesV5(n.config.ClusterConfig.BootNodes))) 389 } 390 391 if len(discoveries) == 0 { 392 return errors.New("wasn't able to register any discovery") 393 } else if len(discoveries) > 1 { 394 n.discovery = discovery.NewMultiplexer(discoveries) 395 } else { 396 n.discovery = discoveries[0] 397 } 398 log.Debug( 399 "using discovery", 400 "instance", reflect.TypeOf(n.discovery), 401 "registerTopics", n.config.RegisterTopics, 402 "requireTopics", n.config.RequireTopics, 403 ) 404 n.register = peers.NewRegister(n.discovery, n.config.RegisterTopics...) 405 options := peers.NewDefaultOptions() 406 // TODO(dshulyak) consider adding a flag to define this behaviour 407 options.AllowStop = len(n.config.RegisterTopics) == 0 408 options.TrustedMailServers = parseNodesToNodeID(n.config.ClusterConfig.TrustedMailServers) 409 410 n.peerPool = peers.NewPeerPool( 411 n.discovery, 412 n.config.RequireTopics, 413 peers.NewCache(n.db), 414 options, 415 ) 416 if err := n.discovery.Start(); err != nil { 417 return err 418 } 419 if err := n.register.Start(); err != nil { 420 return err 421 } 422 return n.peerPool.Start(n.gethNode.Server()) 423 } 424 425 // Stop will stop current StatusNode. A stopped node cannot be resumed. 426 func (n *StatusNode) Stop() error { 427 n.mu.Lock() 428 defer n.mu.Unlock() 429 430 if !n.isRunning() { 431 return ErrNoRunningNode 432 } 433 434 return n.stop() 435 } 436 437 // stop will stop current StatusNode. A stopped node cannot be resumed. 438 func (n *StatusNode) stop() error { 439 if n.isDiscoveryRunning() { 440 if err := n.stopDiscovery(); err != nil { 441 n.log.Error("Error stopping the discovery components", "error", err) 442 } 443 n.register = nil 444 n.peerPool = nil 445 n.discovery = nil 446 } 447 448 if err := n.gethNode.Close(); err != nil { 449 return err 450 } 451 452 n.rpcClient = nil 453 // We need to clear `gethNode` because config is passed to `Start()` 454 // and may be completely different. Similarly with `config`. 455 n.gethNode = nil 456 n.config = nil 457 458 err := n.httpServer.Stop() 459 if err != nil { 460 return err 461 } 462 n.httpServer = nil 463 464 n.downloader.Stop() 465 n.downloader = nil 466 467 if n.db != nil { 468 if err = n.db.Close(); err != nil { 469 n.log.Error("Error closing the leveldb of status node", "error", err) 470 return err 471 } 472 n.db = nil 473 } 474 475 n.rpcFiltersSrvc = nil 476 n.subscriptionsSrvc = nil 477 n.rpcStatsSrvc = nil 478 n.accountsSrvc = nil 479 n.browsersSrvc = nil 480 n.permissionsSrvc = nil 481 n.mailserversSrvc = nil 482 n.providerSrvc = nil 483 n.appMetricsSrvc = nil 484 n.walletSrvc = nil 485 n.peerSrvc = nil 486 n.localNotificationsSrvc = nil 487 n.personalSrvc = nil 488 n.timeSourceSrvc = nil 489 n.wakuSrvc = nil 490 n.wakuExtSrvc = nil 491 n.wakuV2Srvc = nil 492 n.wakuV2ExtSrvc = nil 493 n.ensSrvc = nil 494 n.communityTokensSrvc = nil 495 n.stickersSrvc = nil 496 n.connectorSrvc = nil 497 n.publicMethods = make(map[string]bool) 498 n.pendingTracker = nil 499 n.appGeneralSrvc = nil 500 n.log.Debug("status node stopped") 501 return nil 502 } 503 504 func (n *StatusNode) isDiscoveryRunning() bool { 505 return n.register != nil || n.peerPool != nil || n.discovery != nil 506 } 507 508 func (n *StatusNode) stopDiscovery() error { 509 n.register.Stop() 510 n.peerPool.Stop() 511 return n.discovery.Stop() 512 } 513 514 // ResetChainData removes chain data if node is not running. 515 func (n *StatusNode) ResetChainData(config *params.NodeConfig) error { 516 n.mu.Lock() 517 defer n.mu.Unlock() 518 519 if n.isRunning() { 520 return ErrNodeRunning 521 } 522 523 chainDataDir := filepath.Join(config.DataDir, config.Name, "lightchaindata") 524 if _, err := os.Stat(chainDataDir); os.IsNotExist(err) { 525 return err 526 } 527 err := os.RemoveAll(chainDataDir) 528 if err == nil { 529 n.log.Info("Chain data has been removed", "dir", chainDataDir) 530 } 531 return err 532 } 533 534 // IsRunning confirm that node is running. 535 func (n *StatusNode) IsRunning() bool { 536 n.mu.RLock() 537 defer n.mu.RUnlock() 538 539 return n.isRunning() 540 } 541 542 func (n *StatusNode) isRunning() bool { 543 return n.gethNode != nil && n.gethNode.Server() != nil 544 } 545 546 // populateStaticPeers connects current node with our publicly available LES/SHH/Swarm cluster 547 func (n *StatusNode) populateStaticPeers() error { 548 if !n.config.ClusterConfig.Enabled { 549 n.log.Info("Static peers are disabled") 550 return nil 551 } 552 553 for _, enode := range n.config.ClusterConfig.StaticNodes { 554 if err := n.addPeer(enode); err != nil { 555 n.log.Error("Static peer addition failed", "error", err) 556 return err 557 } 558 n.log.Info("Static peer added", "enode", enode) 559 } 560 561 return nil 562 } 563 564 func (n *StatusNode) removeStaticPeers() error { 565 if !n.config.ClusterConfig.Enabled { 566 n.log.Info("Static peers are disabled") 567 return nil 568 } 569 570 for _, enode := range n.config.ClusterConfig.StaticNodes { 571 if err := n.removePeer(enode); err != nil { 572 n.log.Error("Static peer deletion failed", "error", err) 573 return err 574 } 575 n.log.Info("Static peer deleted", "enode", enode) 576 } 577 return nil 578 } 579 580 // ReconnectStaticPeers removes and adds static peers to a server. 581 func (n *StatusNode) ReconnectStaticPeers() error { 582 n.mu.Lock() 583 defer n.mu.Unlock() 584 585 if !n.isRunning() { 586 return ErrNoRunningNode 587 } 588 589 if err := n.removeStaticPeers(); err != nil { 590 return err 591 } 592 593 return n.populateStaticPeers() 594 } 595 596 // AddPeer adds new static peer node 597 func (n *StatusNode) AddPeer(url string) error { 598 n.mu.RLock() 599 defer n.mu.RUnlock() 600 601 return n.addPeer(url) 602 } 603 604 // addPeer adds new static peer node 605 func (n *StatusNode) addPeer(url string) error { 606 parsedNode, err := enode.ParseV4(url) 607 if err != nil { 608 return err 609 } 610 611 if !n.isRunning() { 612 return ErrNoRunningNode 613 } 614 615 n.gethNode.Server().AddPeer(parsedNode) 616 617 return nil 618 } 619 620 func (n *StatusNode) removePeer(url string) error { 621 parsedNode, err := enode.ParseV4(url) 622 if err != nil { 623 return err 624 } 625 626 if !n.isRunning() { 627 return ErrNoRunningNode 628 } 629 630 n.gethNode.Server().RemovePeer(parsedNode) 631 632 return nil 633 } 634 635 // PeerCount returns the number of connected peers. 636 func (n *StatusNode) PeerCount() int { 637 n.mu.RLock() 638 defer n.mu.RUnlock() 639 640 if !n.isRunning() { 641 return 0 642 } 643 644 return n.gethNode.Server().PeerCount() 645 } 646 647 func (n *StatusNode) ConnectionChanged(state connection.State) { 648 if n.wakuExtSrvc != nil { 649 n.wakuExtSrvc.ConnectionChanged(state) 650 } 651 652 if n.wakuV2ExtSrvc != nil { 653 n.wakuV2ExtSrvc.ConnectionChanged(state) 654 } 655 } 656 657 // AccountManager exposes reference to node's accounts manager 658 func (n *StatusNode) AccountManager() (*accounts.Manager, error) { 659 n.mu.RLock() 660 defer n.mu.RUnlock() 661 662 if n.gethNode == nil { 663 return nil, ErrNoGethNode 664 } 665 666 return n.gethNode.AccountManager(), nil 667 } 668 669 // RPCClient exposes reference to RPC client connected to the running node. 670 func (n *StatusNode) RPCClient() *rpc.Client { 671 n.mu.RLock() 672 defer n.mu.RUnlock() 673 return n.rpcClient 674 } 675 676 // Discover sets up the discovery for a specific topic. 677 func (n *StatusNode) Discover(topic string, max, min int) (err error) { 678 if n.peerPool == nil { 679 return errors.New("peerPool not running") 680 } 681 return n.peerPool.UpdateTopic(topic, params.Limits{ 682 Max: max, 683 Min: min, 684 }) 685 } 686 687 func (n *StatusNode) SetAppDB(db *sql.DB) { 688 n.appDB = db 689 } 690 691 func (n *StatusNode) SetMultiaccountsDB(db *multiaccounts.Database) { 692 n.multiaccountsDB = db 693 } 694 695 func (n *StatusNode) SetWalletDB(db *sql.DB) { 696 n.walletDB = db 697 }