github.com/zhiqiangxu/go-ethereum@v1.9.16-0.20210824055606-be91cfdebc48/node/node.go (about) 1 // Copyright 2015 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package node 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "net" 24 "net/http" 25 "os" 26 "path/filepath" 27 "reflect" 28 "strings" 29 "sync" 30 31 "github.com/prometheus/tsdb/fileutil" 32 "github.com/zhiqiangxu/go-ethereum/accounts" 33 "github.com/zhiqiangxu/go-ethereum/core/rawdb" 34 "github.com/zhiqiangxu/go-ethereum/ethdb" 35 "github.com/zhiqiangxu/go-ethereum/event" 36 "github.com/zhiqiangxu/go-ethereum/internal/debug" 37 "github.com/zhiqiangxu/go-ethereum/log" 38 "github.com/zhiqiangxu/go-ethereum/p2p" 39 "github.com/zhiqiangxu/go-ethereum/rpc" 40 ) 41 42 // Node is a container on which services can be registered. 43 type Node struct { 44 eventmux *event.TypeMux // Event multiplexer used between the services of a stack 45 config *Config 46 accman *accounts.Manager 47 48 ephemeralKeystore string // if non-empty, the key directory that will be removed by Stop 49 instanceDirLock fileutil.Releaser // prevents concurrent use of instance directory 50 51 serverConfig p2p.Config 52 server *p2p.Server // Currently running P2P networking layer 53 54 serviceFuncs []ServiceConstructor // Service constructors (in dependency order) 55 services map[reflect.Type]Service // Currently running services 56 57 rpcAPIs []rpc.API // List of APIs currently provided by the node 58 inprocHandler *rpc.Server // In-process RPC request handler to process the API requests 59 60 ipcEndpoint string // IPC endpoint to listen at (empty = IPC disabled) 61 ipcListener net.Listener // IPC RPC listener socket to serve API requests 62 ipcHandler *rpc.Server // IPC RPC request handler to process the API requests 63 64 httpEndpoint string // HTTP endpoint (interface + port) to listen at (empty = HTTP disabled) 65 httpWhitelist []string // HTTP RPC modules to allow through this endpoint 66 httpListenerAddr net.Addr // Address of HTTP RPC listener socket serving API requests 67 httpServer *http.Server // HTTP RPC HTTP server 68 httpHandler *rpc.Server // HTTP RPC request handler to process the API requests 69 70 wsEndpoint string // WebSocket endpoint (interface + port) to listen at (empty = WebSocket disabled) 71 wsListenerAddr net.Addr // Address of WebSocket RPC listener socket serving API requests 72 wsHTTPServer *http.Server // WebSocket RPC HTTP server 73 wsHandler *rpc.Server // WebSocket RPC request handler to process the API requests 74 75 stop chan struct{} // Channel to wait for termination notifications 76 lock sync.RWMutex 77 78 log log.Logger 79 } 80 81 // New creates a new P2P node, ready for protocol registration. 82 func New(conf *Config) (*Node, error) { 83 // Copy config and resolve the datadir so future changes to the current 84 // working directory don't affect the node. 85 confCopy := *conf 86 conf = &confCopy 87 if conf.DataDir != "" { 88 absdatadir, err := filepath.Abs(conf.DataDir) 89 if err != nil { 90 return nil, err 91 } 92 conf.DataDir = absdatadir 93 } 94 // Ensure that the instance name doesn't cause weird conflicts with 95 // other files in the data directory. 96 if strings.ContainsAny(conf.Name, `/\`) { 97 return nil, errors.New(`Config.Name must not contain '/' or '\'`) 98 } 99 if conf.Name == datadirDefaultKeyStore { 100 return nil, errors.New(`Config.Name cannot be "` + datadirDefaultKeyStore + `"`) 101 } 102 if strings.HasSuffix(conf.Name, ".ipc") { 103 return nil, errors.New(`Config.Name cannot end in ".ipc"`) 104 } 105 // Ensure that the AccountManager method works before the node has started. 106 // We rely on this in cmd/geth. 107 am, ephemeralKeystore, err := makeAccountManager(conf) 108 if err != nil { 109 return nil, err 110 } 111 if conf.Logger == nil { 112 conf.Logger = log.New() 113 } 114 // Note: any interaction with Config that would create/touch files 115 // in the data directory or instance directory is delayed until Start. 116 return &Node{ 117 accman: am, 118 ephemeralKeystore: ephemeralKeystore, 119 config: conf, 120 serviceFuncs: []ServiceConstructor{}, 121 ipcEndpoint: conf.IPCEndpoint(), 122 httpEndpoint: conf.HTTPEndpoint(), 123 wsEndpoint: conf.WSEndpoint(), 124 eventmux: new(event.TypeMux), 125 log: conf.Logger, 126 }, nil 127 } 128 129 // Close stops the Node and releases resources acquired in 130 // Node constructor New. 131 func (n *Node) Close() error { 132 var errs []error 133 134 // Terminate all subsystems and collect any errors 135 if err := n.Stop(); err != nil && err != ErrNodeStopped { 136 errs = append(errs, err) 137 } 138 if err := n.accman.Close(); err != nil { 139 errs = append(errs, err) 140 } 141 // Report any errors that might have occurred 142 switch len(errs) { 143 case 0: 144 return nil 145 case 1: 146 return errs[0] 147 default: 148 return fmt.Errorf("%v", errs) 149 } 150 } 151 152 // Register injects a new service into the node's stack. The service created by 153 // the passed constructor must be unique in its type with regard to sibling ones. 154 func (n *Node) Register(constructor ServiceConstructor) error { 155 n.lock.Lock() 156 defer n.lock.Unlock() 157 158 if n.server != nil { 159 return ErrNodeRunning 160 } 161 n.serviceFuncs = append(n.serviceFuncs, constructor) 162 return nil 163 } 164 165 // Start creates a live P2P node and starts running it. 166 func (n *Node) Start() error { 167 n.lock.Lock() 168 defer n.lock.Unlock() 169 170 // Short circuit if the node's already running 171 if n.server != nil { 172 return ErrNodeRunning 173 } 174 if err := n.openDataDir(); err != nil { 175 return err 176 } 177 178 // Initialize the p2p server. This creates the node key and 179 // discovery databases. 180 n.serverConfig = n.config.P2P 181 n.serverConfig.PrivateKey = n.config.NodeKey() 182 n.serverConfig.Name = n.config.NodeName() 183 n.serverConfig.Logger = n.log 184 if n.serverConfig.StaticNodes == nil { 185 n.serverConfig.StaticNodes = n.config.StaticNodes() 186 } 187 if n.serverConfig.TrustedNodes == nil { 188 n.serverConfig.TrustedNodes = n.config.TrustedNodes() 189 } 190 if n.serverConfig.NodeDatabase == "" { 191 n.serverConfig.NodeDatabase = n.config.NodeDB() 192 } 193 running := &p2p.Server{Config: n.serverConfig} 194 n.log.Info("Starting peer-to-peer node", "instance", n.serverConfig.Name) 195 196 // Otherwise copy and specialize the P2P configuration 197 services := make(map[reflect.Type]Service) 198 for _, constructor := range n.serviceFuncs { 199 // Create a new context for the particular service 200 ctx := &ServiceContext{ 201 Config: *n.config, 202 services: make(map[reflect.Type]Service), 203 EventMux: n.eventmux, 204 AccountManager: n.accman, 205 } 206 for kind, s := range services { // copy needed for threaded access 207 ctx.services[kind] = s 208 } 209 // Construct and save the service 210 service, err := constructor(ctx) 211 if err != nil { 212 return err 213 } 214 kind := reflect.TypeOf(service) 215 if _, exists := services[kind]; exists { 216 return &DuplicateServiceError{Kind: kind} 217 } 218 services[kind] = service 219 } 220 // Gather the protocols and start the freshly assembled P2P server 221 for _, service := range services { 222 running.Protocols = append(running.Protocols, service.Protocols()...) 223 } 224 if err := running.Start(); err != nil { 225 return convertFileLockError(err) 226 } 227 // Start each of the services 228 var started []reflect.Type 229 for kind, service := range services { 230 // Start the next service, stopping all previous upon failure 231 if err := service.Start(running); err != nil { 232 for _, kind := range started { 233 services[kind].Stop() 234 } 235 running.Stop() 236 237 return err 238 } 239 // Mark the service started for potential cleanup 240 started = append(started, kind) 241 } 242 // Lastly, start the configured RPC interfaces 243 if err := n.startRPC(services); err != nil { 244 for _, service := range services { 245 service.Stop() 246 } 247 running.Stop() 248 return err 249 } 250 // Finish initializing the startup 251 n.services = services 252 n.server = running 253 n.stop = make(chan struct{}) 254 return nil 255 } 256 257 // Config returns the configuration of node. 258 func (n *Node) Config() *Config { 259 return n.config 260 } 261 262 func (n *Node) openDataDir() error { 263 if n.config.DataDir == "" { 264 return nil // ephemeral 265 } 266 267 instdir := filepath.Join(n.config.DataDir, n.config.name()) 268 if err := os.MkdirAll(instdir, 0700); err != nil { 269 return err 270 } 271 // Lock the instance directory to prevent concurrent use by another instance as well as 272 // accidental use of the instance directory as a database. 273 release, _, err := fileutil.Flock(filepath.Join(instdir, "LOCK")) 274 if err != nil { 275 return convertFileLockError(err) 276 } 277 n.instanceDirLock = release 278 return nil 279 } 280 281 // startRPC is a helper method to start all the various RPC endpoints during node 282 // startup. It's not meant to be called at any time afterwards as it makes certain 283 // assumptions about the state of the node. 284 func (n *Node) startRPC(services map[reflect.Type]Service) error { 285 // Gather all the possible APIs to surface 286 apis := n.apis() 287 for _, service := range services { 288 apis = append(apis, service.APIs()...) 289 } 290 // Start the various API endpoints, terminating all in case of errors 291 if err := n.startInProc(apis); err != nil { 292 return err 293 } 294 if err := n.startIPC(apis); err != nil { 295 n.stopInProc() 296 return err 297 } 298 if err := n.startHTTP(n.httpEndpoint, apis, n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPVirtualHosts, n.config.HTTPTimeouts, n.config.WSOrigins); err != nil { 299 n.stopIPC() 300 n.stopInProc() 301 return err 302 } 303 // if endpoints are not the same, start separate servers 304 if n.httpEndpoint != n.wsEndpoint { 305 if err := n.startWS(n.wsEndpoint, apis, n.config.WSModules, n.config.WSOrigins, n.config.WSExposeAll); err != nil { 306 n.stopHTTP() 307 n.stopIPC() 308 n.stopInProc() 309 return err 310 } 311 } 312 313 // All API endpoints started successfully 314 n.rpcAPIs = apis 315 return nil 316 } 317 318 // startInProc initializes an in-process RPC endpoint. 319 func (n *Node) startInProc(apis []rpc.API) error { 320 // Register all the APIs exposed by the services 321 handler := rpc.NewServer() 322 for _, api := range apis { 323 if err := handler.RegisterName(api.Namespace, api.Service); err != nil { 324 return err 325 } 326 n.log.Debug("InProc registered", "namespace", api.Namespace) 327 } 328 n.inprocHandler = handler 329 return nil 330 } 331 332 // stopInProc terminates the in-process RPC endpoint. 333 func (n *Node) stopInProc() { 334 if n.inprocHandler != nil { 335 n.inprocHandler.Stop() 336 n.inprocHandler = nil 337 } 338 } 339 340 // startIPC initializes and starts the IPC RPC endpoint. 341 func (n *Node) startIPC(apis []rpc.API) error { 342 if n.ipcEndpoint == "" { 343 return nil // IPC disabled. 344 } 345 listener, handler, err := rpc.StartIPCEndpoint(n.ipcEndpoint, apis) 346 if err != nil { 347 return err 348 } 349 n.ipcListener = listener 350 n.ipcHandler = handler 351 n.log.Info("IPC endpoint opened", "url", n.ipcEndpoint) 352 return nil 353 } 354 355 // stopIPC terminates the IPC RPC endpoint. 356 func (n *Node) stopIPC() { 357 if n.ipcListener != nil { 358 n.ipcListener.Close() 359 n.ipcListener = nil 360 361 n.log.Info("IPC endpoint closed", "url", n.ipcEndpoint) 362 } 363 if n.ipcHandler != nil { 364 n.ipcHandler.Stop() 365 n.ipcHandler = nil 366 } 367 } 368 369 // startHTTP initializes and starts the HTTP RPC endpoint. 370 func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors []string, vhosts []string, timeouts rpc.HTTPTimeouts, wsOrigins []string) error { 371 // Short circuit if the HTTP endpoint isn't being exposed 372 if endpoint == "" { 373 return nil 374 } 375 // register apis and create handler stack 376 srv := rpc.NewServer() 377 err := RegisterApisFromWhitelist(apis, modules, srv, false) 378 if err != nil { 379 return err 380 } 381 handler := NewHTTPHandlerStack(srv, cors, vhosts) 382 // wrap handler in WebSocket handler only if WebSocket port is the same as http rpc 383 if n.httpEndpoint == n.wsEndpoint { 384 handler = NewWebsocketUpgradeHandler(handler, srv.WebsocketHandler(wsOrigins)) 385 } 386 httpServer, addr, err := StartHTTPEndpoint(endpoint, timeouts, handler) 387 if err != nil { 388 return err 389 } 390 n.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", addr), 391 "cors", strings.Join(cors, ","), 392 "vhosts", strings.Join(vhosts, ",")) 393 if n.httpEndpoint == n.wsEndpoint { 394 n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", addr)) 395 } 396 // All listeners booted successfully 397 n.httpEndpoint = endpoint 398 n.httpListenerAddr = addr 399 n.httpServer = httpServer 400 n.httpHandler = srv 401 402 return nil 403 } 404 405 // stopHTTP terminates the HTTP RPC endpoint. 406 func (n *Node) stopHTTP() { 407 if n.httpServer != nil { 408 // Don't bother imposing a timeout here. 409 n.httpServer.Shutdown(context.Background()) 410 n.log.Info("HTTP endpoint closed", "url", fmt.Sprintf("http://%v/", n.httpListenerAddr)) 411 } 412 if n.httpHandler != nil { 413 n.httpHandler.Stop() 414 n.httpHandler = nil 415 } 416 } 417 418 // startWS initializes and starts the WebSocket RPC endpoint. 419 func (n *Node) startWS(endpoint string, apis []rpc.API, modules []string, wsOrigins []string, exposeAll bool) error { 420 // Short circuit if the WS endpoint isn't being exposed 421 if endpoint == "" { 422 return nil 423 } 424 425 srv := rpc.NewServer() 426 handler := srv.WebsocketHandler(wsOrigins) 427 err := RegisterApisFromWhitelist(apis, modules, srv, exposeAll) 428 if err != nil { 429 return err 430 } 431 httpServer, addr, err := startWSEndpoint(endpoint, handler) 432 if err != nil { 433 return err 434 } 435 n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", addr)) 436 // All listeners booted successfully 437 n.wsEndpoint = endpoint 438 n.wsListenerAddr = addr 439 n.wsHTTPServer = httpServer 440 n.wsHandler = srv 441 442 return nil 443 } 444 445 // stopWS terminates the WebSocket RPC endpoint. 446 func (n *Node) stopWS() { 447 if n.wsHTTPServer != nil { 448 // Don't bother imposing a timeout here. 449 n.wsHTTPServer.Shutdown(context.Background()) 450 n.log.Info("WebSocket endpoint closed", "url", fmt.Sprintf("ws://%v", n.wsListenerAddr)) 451 } 452 if n.wsHandler != nil { 453 n.wsHandler.Stop() 454 n.wsHandler = nil 455 } 456 } 457 458 // Stop terminates a running node along with all it's services. In the node was 459 // not started, an error is returned. 460 func (n *Node) Stop() error { 461 n.lock.Lock() 462 defer n.lock.Unlock() 463 464 // Short circuit if the node's not running 465 if n.server == nil { 466 return ErrNodeStopped 467 } 468 469 // Terminate the API, services and the p2p server. 470 n.stopWS() 471 n.stopHTTP() 472 n.stopIPC() 473 n.rpcAPIs = nil 474 failure := &StopError{ 475 Services: make(map[reflect.Type]error), 476 } 477 for kind, service := range n.services { 478 if err := service.Stop(); err != nil { 479 failure.Services[kind] = err 480 } 481 } 482 n.server.Stop() 483 n.services = nil 484 n.server = nil 485 486 // Release instance directory lock. 487 if n.instanceDirLock != nil { 488 if err := n.instanceDirLock.Release(); err != nil { 489 n.log.Error("Can't release datadir lock", "err", err) 490 } 491 n.instanceDirLock = nil 492 } 493 494 // unblock n.Wait 495 close(n.stop) 496 497 // Remove the keystore if it was created ephemerally. 498 var keystoreErr error 499 if n.ephemeralKeystore != "" { 500 keystoreErr = os.RemoveAll(n.ephemeralKeystore) 501 } 502 503 if len(failure.Services) > 0 { 504 return failure 505 } 506 if keystoreErr != nil { 507 return keystoreErr 508 } 509 return nil 510 } 511 512 // Wait blocks the thread until the node is stopped. If the node is not running 513 // at the time of invocation, the method immediately returns. 514 func (n *Node) Wait() { 515 n.lock.RLock() 516 if n.server == nil { 517 n.lock.RUnlock() 518 return 519 } 520 stop := n.stop 521 n.lock.RUnlock() 522 523 <-stop 524 } 525 526 // Restart terminates a running node and boots up a new one in its place. If the 527 // node isn't running, an error is returned. 528 func (n *Node) Restart() error { 529 if err := n.Stop(); err != nil { 530 return err 531 } 532 if err := n.Start(); err != nil { 533 return err 534 } 535 return nil 536 } 537 538 // Attach creates an RPC client attached to an in-process API handler. 539 func (n *Node) Attach() (*rpc.Client, error) { 540 n.lock.RLock() 541 defer n.lock.RUnlock() 542 543 if n.server == nil { 544 return nil, ErrNodeStopped 545 } 546 return rpc.DialInProc(n.inprocHandler), nil 547 } 548 549 // RPCHandler returns the in-process RPC request handler. 550 func (n *Node) RPCHandler() (*rpc.Server, error) { 551 n.lock.RLock() 552 defer n.lock.RUnlock() 553 554 if n.inprocHandler == nil { 555 return nil, ErrNodeStopped 556 } 557 return n.inprocHandler, nil 558 } 559 560 // Server retrieves the currently running P2P network layer. This method is meant 561 // only to inspect fields of the currently running server, life cycle management 562 // should be left to this Node entity. 563 func (n *Node) Server() *p2p.Server { 564 n.lock.RLock() 565 defer n.lock.RUnlock() 566 567 return n.server 568 } 569 570 // Service retrieves a currently running service registered of a specific type. 571 func (n *Node) Service(service interface{}) error { 572 n.lock.RLock() 573 defer n.lock.RUnlock() 574 575 // Short circuit if the node's not running 576 if n.server == nil { 577 return ErrNodeStopped 578 } 579 // Otherwise try to find the service to return 580 element := reflect.ValueOf(service).Elem() 581 if running, ok := n.services[element.Type()]; ok { 582 element.Set(reflect.ValueOf(running)) 583 return nil 584 } 585 return ErrServiceUnknown 586 } 587 588 // DataDir retrieves the current datadir used by the protocol stack. 589 // Deprecated: No files should be stored in this directory, use InstanceDir instead. 590 func (n *Node) DataDir() string { 591 return n.config.DataDir 592 } 593 594 // InstanceDir retrieves the instance directory used by the protocol stack. 595 func (n *Node) InstanceDir() string { 596 return n.config.instanceDir() 597 } 598 599 // AccountManager retrieves the account manager used by the protocol stack. 600 func (n *Node) AccountManager() *accounts.Manager { 601 return n.accman 602 } 603 604 // IPCEndpoint retrieves the current IPC endpoint used by the protocol stack. 605 func (n *Node) IPCEndpoint() string { 606 return n.ipcEndpoint 607 } 608 609 // HTTPEndpoint retrieves the current HTTP endpoint used by the protocol stack. 610 func (n *Node) HTTPEndpoint() string { 611 n.lock.Lock() 612 defer n.lock.Unlock() 613 614 if n.httpListenerAddr != nil { 615 return n.httpListenerAddr.String() 616 } 617 return n.httpEndpoint 618 } 619 620 // WSEndpoint retrieves the current WS endpoint used by the protocol stack. 621 func (n *Node) WSEndpoint() string { 622 n.lock.Lock() 623 defer n.lock.Unlock() 624 625 if n.wsListenerAddr != nil { 626 return n.wsListenerAddr.String() 627 } 628 return n.wsEndpoint 629 } 630 631 // EventMux retrieves the event multiplexer used by all the network services in 632 // the current protocol stack. 633 func (n *Node) EventMux() *event.TypeMux { 634 return n.eventmux 635 } 636 637 // OpenDatabase opens an existing database with the given name (or creates one if no 638 // previous can be found) from within the node's instance directory. If the node is 639 // ephemeral, a memory database is returned. 640 func (n *Node) OpenDatabase(name string, cache, handles int, namespace string) (ethdb.Database, error) { 641 if n.config.DataDir == "" { 642 return rawdb.NewMemoryDatabase(), nil 643 } 644 return rawdb.NewLevelDBDatabase(n.config.ResolvePath(name), cache, handles, namespace) 645 } 646 647 // OpenDatabaseWithFreezer opens an existing database with the given name (or 648 // creates one if no previous can be found) from within the node's data directory, 649 // also attaching a chain freezer to it that moves ancient chain data from the 650 // database to immutable append-only files. If the node is an ephemeral one, a 651 // memory database is returned. 652 func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, namespace string) (ethdb.Database, error) { 653 if n.config.DataDir == "" { 654 return rawdb.NewMemoryDatabase(), nil 655 } 656 root := n.config.ResolvePath(name) 657 658 switch { 659 case freezer == "": 660 freezer = filepath.Join(root, "ancient") 661 case !filepath.IsAbs(freezer): 662 freezer = n.config.ResolvePath(freezer) 663 } 664 return rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace) 665 } 666 667 // ResolvePath returns the absolute path of a resource in the instance directory. 668 func (n *Node) ResolvePath(x string) string { 669 return n.config.ResolvePath(x) 670 } 671 672 // apis returns the collection of RPC descriptors this node offers. 673 func (n *Node) apis() []rpc.API { 674 return []rpc.API{ 675 { 676 Namespace: "admin", 677 Version: "1.0", 678 Service: NewPrivateAdminAPI(n), 679 }, { 680 Namespace: "admin", 681 Version: "1.0", 682 Service: NewPublicAdminAPI(n), 683 Public: true, 684 }, { 685 Namespace: "debug", 686 Version: "1.0", 687 Service: debug.Handler, 688 }, { 689 Namespace: "web3", 690 Version: "1.0", 691 Service: NewPublicWeb3API(n), 692 Public: true, 693 }, 694 } 695 } 696 697 // RegisterApisFromWhitelist checks the given modules' availability, generates a whitelist based on the allowed modules, 698 // and then registers all of the APIs exposed by the services. 699 func RegisterApisFromWhitelist(apis []rpc.API, modules []string, srv *rpc.Server, exposeAll bool) error { 700 if bad, available := checkModuleAvailability(modules, apis); len(bad) > 0 { 701 log.Error("Unavailable modules in HTTP API list", "unavailable", bad, "available", available) 702 } 703 // Generate the whitelist based on the allowed modules 704 whitelist := make(map[string]bool) 705 for _, module := range modules { 706 whitelist[module] = true 707 } 708 // Register all the APIs exposed by the services 709 for _, api := range apis { 710 if exposeAll || whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) { 711 if err := srv.RegisterName(api.Namespace, api.Service); err != nil { 712 return err 713 } 714 } 715 } 716 return nil 717 }