diff --git a/node/errors.go b/node/errors.go index 2e0dadc4d6b3..67547bf691f1 100644 --- a/node/errors.go +++ b/node/errors.go @@ -39,17 +39,6 @@ func convertFileLockError(err error) error { return err } -// DuplicateServiceError is returned during Node startup if a registered service -// constructor returns a service of the same type that was already started. -type DuplicateServiceError struct { - Kind reflect.Type -} - -// Error generates a textual representation of the duplicate service error. -func (e *DuplicateServiceError) Error() string { - return fmt.Sprintf("duplicate service: %v", e.Kind) -} - // StopError is returned if a Node fails to stop either any of its registered // services or itself. type StopError struct { diff --git a/node/node.go b/node/node.go index 2b477c616f25..6a94f1c0dee7 100644 --- a/node/node.go +++ b/node/node.go @@ -53,7 +53,7 @@ type Node struct { ServiceContext *ServiceContext - lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle + lifecycles map[reflect.Type]Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle rpcAPIs []rpc.API // List of APIs currently provided by the node inprocHandler *rpc.Server // In-process RPC request handler to process the API requests @@ -107,8 +107,10 @@ func New(conf *Config) (*Node, error) { accman: am, ephemeralKeystore: ephemeralKeystore, config: conf, + lifecycles: make(map[reflect.Type]Lifecycle), ServiceContext: &ServiceContext{ Config: *conf, + Lifecycles: make(map[reflect.Type]Lifecycle), }, httpServers: make([]*HTTPServer, 0), ipc: &HTTPServer{ @@ -199,12 +201,12 @@ func (n *Node) Close() error { // RegisterLifecycle registers the given Lifecycle on the node func (n *Node) RegisterLifecycle(lifecycle Lifecycle) { - for _, existing := range n.lifecycles { - if existing == lifecycle { - Fatalf("Lifecycle cannot be registered more than once", lifecycle) - } + kind := reflect.TypeOf(lifecycle) + if _, exists := n.lifecycles[kind]; exists { + Fatalf("Lifecycle cannot be registered more than once", kind) } - n.lifecycles = append(n.lifecycles, lifecycle) + + n.lifecycles[kind] = lifecycle } // RegisterProtocols adds backend's protocols to the node's p2p server @@ -265,7 +267,7 @@ func (n *Node) CreateHTTPServer(h *HTTPServer, exposeAll bool) error { // running returns true if the node's p2p server is already running func (n *Node) running() bool { - return n.server.Listening() + return n.server.Running() } // Start creates a live P2P node and starts running it. @@ -289,9 +291,8 @@ func (n *Node) Start() error { // TODO running p2p server needs to somehow be added to the backend - // Start the configured RPC interfaces - if err := n.startRPC(); err != nil { - n.stopLifecycles(n.lifecycles) + // Configure the RPC interfaces + if err := n.configureRPC(); err != nil { n.server.Stop() return err } @@ -301,8 +302,11 @@ func (n *Node) Start() error { for _, lifecycle := range n.lifecycles { if err := lifecycle.Start(); err != nil { n.stopLifecycles(started) + n.server.Stop() + return err } started = append(started, lifecycle) + n.ServiceContext.Lifecycles[reflect.TypeOf(lifecycle)] = lifecycle } // Finish initializing the service context @@ -478,15 +482,7 @@ func (n *Node) stopServer(server *HTTPServer) { // removeLifecycle removes a stopped Lifecycle from the running node's Lifecycles func (n *Node) removeLifecycle(lifecycle Lifecycle) { - remainingLifecycles := make([]Lifecycle, len(n.lifecycles)-1) - index := 0 - for _, remaining := range n.lifecycles { - if remaining != lifecycle { - remainingLifecycles[index] = remaining - index ++ - } - } - n.lifecycles = remainingLifecycles + delete(n.lifecycles, reflect.TypeOf(lifecycle)) } // Stop terminates a running node along with all it's services. In the node was @@ -496,7 +492,7 @@ func (n *Node) Stop() error { defer n.lock.Unlock() // Short circuit if the node's not running - if n.server == nil { + if n.server == nil || !n.running() { return ErrNodeStopped } @@ -506,10 +502,11 @@ func (n *Node) Stop() error { failure := &StopError{ Services: make(map[reflect.Type]error), } - for _, lifecycle := range n.lifecycles { + for kind, lifecycle := range n.lifecycles { if err := lifecycle.Stop(); err != nil { failure.Services[reflect.TypeOf(lifecycle)] = err } + delete(n.lifecycles, kind) } n.server.Stop() n.server = nil @@ -554,18 +551,6 @@ func (n *Node) Wait() { <-stop } -// Restart terminates a running node and boots up a new one in its place. If the -// node isn't running, an error is returned. -func (n *Node) Restart() error { - if err := n.Stop(); err != nil { - return err - } - if err := n.Start(); err != nil { - return err - } - return nil -} - // Attach creates an RPC client attached to an in-process API handler. func (n *Node) Attach() (*rpc.Client, error) { n.lock.RLock() @@ -695,6 +680,24 @@ func (n *Node) ResolvePath(x string) string { return n.config.ResolvePath(x) } +// Lifecycle retrieves a currently running Lifecycle registered of a specific type. +func (n *Node) Lifecycle(lifecycle interface{}) error { + n.lock.RLock() + defer n.lock.RUnlock() + + // Short circuit if the node's not running + if !n.running() { + return ErrNodeStopped + } + // Otherwise try to find the service to return + element := reflect.ValueOf(lifecycle).Elem() + if running, ok := n.lifecycles[element.Type()]; ok { + element.Set(reflect.ValueOf(running)) + return nil + } + return ErrServiceUnknown +} + // apis returns the collection of RPC descriptors this node offers. func (n *Node) apis() []rpc.API { return []rpc.API{ diff --git a/node/node_example_test.go b/node/node_example_test.go index ccaa3d990f53..9363bbc18379 100644 --- a/node/node_example_test.go +++ b/node/node_example_test.go @@ -34,7 +34,7 @@ type SampleLifecycle struct{} func (s *SampleLifecycle) Start() error { fmt.Println("Service starting..."); return nil } func (s *SampleLifecycle) Stop() error { fmt.Println("Service stopping..."); return nil } -func ExampleService() { +func ExampleLifecycle() { // Create a network node to run protocols with the default values. stack, err := node.New(&node.Config{}) if err != nil { @@ -50,15 +50,10 @@ func ExampleService() { if err := stack.Start(); err != nil { log.Fatalf("Failed to start the protocol stack: %v", err) } - if err := stack.Restart(); err != nil { - log.Fatalf("Failed to restart the protocol stack: %v", err) - } if err := stack.Stop(); err != nil { log.Fatalf("Failed to stop the protocol stack: %v", err) } // Output: // Service starting... // Service stopping... - // Service starting... - // Service stopping... } diff --git a/node/node_test.go b/node/node_test.go index b5dc12f54f62..e8764f1ab383 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -23,11 +23,9 @@ import ( "os" "reflect" "testing" - "time" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/rpc" "github.com/stretchr/testify/assert" ) @@ -43,7 +41,7 @@ func testNodeConfig() *Config { } } -// Tests that an empty protocol stack can be started, restarted and stopped. +// Tests that an empty protocol stack can be started and stopped. func TestNodeLifeCycle(t *testing.T) { stack, err := New(testNodeConfig()) if err != nil { @@ -64,12 +62,6 @@ func TestNodeLifeCycle(t *testing.T) { if err := stack.Start(); err != ErrNodeRunning { t.Fatalf("start failure mismatch: have %v, want %v ", err, ErrNodeRunning) } - // Ensure that a node can be restarted arbitrarily many times - for i := 0; i < 3; i++ { - if err := stack.Restart(); err != nil { - t.Fatalf("iter %d: failed to restart node: %v", i, err) - } - } // Ensure that a node can be stopped, but only once if err := stack.Stop(); err != nil { t.Fatalf("failed to stop node: %v", err) @@ -112,71 +104,63 @@ func TestNodeUsedDataDir(t *testing.T) { } } -// Tests whether services can be registered and duplicates caught. -func TestServiceRegistry(t *testing.T) { +// Tests whether a Lifecycle can be registered. +func TestLifecycleRegistry(t *testing.T) { stack, err := New(testNodeConfig()) if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } defer stack.Close() - // Register a batch of unique services and ensure they start successfully - noop, err := NewNoop(stack) + noop := NewNoop() + stack.RegisterLifecycle(noop) - if err := stack.Start(); err != nil { - t.Fatalf("failed to start original service stack: %v", err) - } - if err := stack.Stop(); err != nil { - t.Fatalf("failed to stop original service stack: %v", err) - } - // Duplicate one of the services and retry starting the node - stack.RegisterLifecycle(noop) // TODO how to test for a fatal err ? - //err != nil { - // t.Fatalf("duplicate registration failed: %v", err) - //} - if err := stack.Start(); err == nil { - t.Fatalf("duplicate service started") - } else { - if _, ok := err.(*DuplicateServiceError); !ok { - t.Fatalf("duplicate error mismatch: have %v, want %v", err, DuplicateServiceError{}) - } + if _, exists := stack.lifecycles[reflect.TypeOf(noop)]; !exists { + t.Fatalf("lifecycle was not properly registered on the node, %v", err) } } -// Tests that registered services get started and stopped correctly. -func TestServiceLifeCycle(t *testing.T) { +// Tests that registered Lifecycles get started and stopped correctly. +func TestLifecycleLifeCycle(t *testing.T) { stack, err := New(testNodeConfig()) if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } defer stack.Close() - // Register a batch of life-cycle instrumented services - services := map[string]InstrumentingWrapper{ - "A": InstrumentedServiceMakerA, - "B": InstrumentedServiceMakerB, - "C": InstrumentedServiceMakerC, - } started := make(map[string]bool) stopped := make(map[string]bool) - for id, maker := range services { - id := id // Closure for the constructor - constructor := func(*ServiceContext) (Service, error) { - return &InstrumentedService{ - startHook: func(*p2p.Server) { started[id] = true }, - stopHook: func() { stopped[id] = true }, - }, nil - } - if err := stack.Register(maker(constructor)); err != nil { - t.Fatalf("service %s: registration failed: %v", id, err) - } + // Create a batch of instrumented services + lifecycles := map[string]Lifecycle{ + "A": &InstrumentedServiceA{ + InstrumentedService{ + startHook: func() { started["A"] = true }, + stopHook: func() { stopped["A"] = true }, + }, + }, + "B": &InstrumentedServiceB{ + InstrumentedService{ + startHook: func() { started["B"] = true }, + stopHook: func() { stopped["B"] = true }, + }, + }, + "C": &InstrumentedServiceC{ + InstrumentedService{ + startHook: func() { started["C"] = true }, + stopHook: func() { stopped["C"] = true }, + }, + }, + } + // register lifecycles on node + for _, lifecycle := range lifecycles { + stack.RegisterLifecycle(lifecycle) } // Start the node and check that all services are running if err := stack.Start(); err != nil { t.Fatalf("failed to start protocol stack: %v", err) } - for id := range services { + for id := range lifecycles { if !started[id] { t.Fatalf("service %s: freshly started service not running", id) } @@ -188,157 +172,62 @@ func TestServiceLifeCycle(t *testing.T) { if err := stack.Stop(); err != nil { t.Fatalf("failed to stop protocol stack: %v", err) } - for id := range services { + for id := range lifecycles { if !stopped[id] { t.Fatalf("service %s: freshly terminated service still running", id) } } } -// Tests that services are restarted cleanly as new instances. -func TestServiceRestarts(t *testing.T) { +// Tests that if a Lifecycle fails to start, all others started before it will be +// shut down. +func TestLifecycleStartupAbortion(t *testing.T) { stack, err := New(testNodeConfig()) if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } defer stack.Close() - // Define a service that does not support restarts - var ( - running bool - started int - ) - constructor := func(*ServiceContext) (Service, error) { - running = false - - return &InstrumentedService{ - startHook: func(*p2p.Server) { - if running { - panic("already running") - } - running = true - started++ - }, - }, nil - } - // Register the service and start the protocol stack - if err := stack.Register(constructor); err != nil { - t.Fatalf("failed to register the service: %v", err) - } - if err := stack.Start(); err != nil { - t.Fatalf("failed to start protocol stack: %v", err) - } - defer stack.Stop() + started := make(map[string]bool) + stopped := make(map[string]bool) - if !running || started != 1 { - t.Fatalf("running/started mismatch: have %v/%d, want true/1", running, started) - } - // Restart the stack a few times and check successful service restarts - for i := 0; i < 3; i++ { - if err := stack.Restart(); err != nil { - t.Fatalf("iter %d: failed to restart stack: %v", i, err) - } - } - if !running || started != 4 { - t.Fatalf("running/started mismatch: have %v/%d, want true/4", running, started) + // Create a batch of instrumented services + lifecycles := map[string]Lifecycle{ + "A": &InstrumentedServiceA{ + InstrumentedService{ + startHook: func() { started["A"] = true }, + stopHook: func() { stopped["A"] = true }, + }, + }, + "B": &InstrumentedServiceB{ + InstrumentedService{ + startHook: func() { started["B"] = true }, + stopHook: func() { stopped["B"] = true }, + }, + }, + "C": &InstrumentedServiceC{ + InstrumentedService{ + startHook: func() { started["C"] = true }, + stopHook: func() { stopped["C"] = true }, + }, + }, } -} - -// Tests that if a service fails to initialize itself, none of the other services -// will be allowed to even start. -func TestServiceConstructionAbortion(t *testing.T) { - stack, err := New(testNodeConfig()) - if err != nil { - t.Fatalf("failed to create protocol stack: %v", err) + // register lifecycles on node + for _, lifecycle := range lifecycles { + stack.RegisterLifecycle(lifecycle) } - defer stack.Close() - // Define a batch of good services - services := map[string]InstrumentingWrapper{ - "A": InstrumentedServiceMakerA, - "B": InstrumentedServiceMakerB, - "C": InstrumentedServiceMakerC, - } - started := make(map[string]bool) - for id, maker := range services { - id := id // Closure for the constructor - constructor := func(*ServiceContext) (Service, error) { - return &InstrumentedService{ - startHook: func(*p2p.Server) { started[id] = true }, - }, nil - } - if err := stack.Register(maker(constructor)); err != nil { - t.Fatalf("service %s: registration failed: %v", id, err) - } - } // Register a service that fails to construct itself failure := errors.New("fail") - failer := func(*ServiceContext) (Service, error) { - return nil, failure - } - if err := stack.Register(failer); err != nil { - t.Fatalf("failer registration failed: %v", err) - } - // Start the protocol stack and ensure none of the services get started - for i := 0; i < 100; i++ { - if err := stack.Start(); err != failure { - t.Fatalf("iter %d: stack startup failure mismatch: have %v, want %v", i, err, failure) - } - for id := range services { - if started[id] { - t.Fatalf("service %s: started should not have", id) - } - delete(started, id) - } - } -} + failer := &InstrumentedService{ start: failure } + stack.RegisterLifecycle(failer) -// Tests that if a service fails to start, all others started before it will be -// shut down. -func TestServiceStartupAbortion(t *testing.T) { - stack, err := New(testNodeConfig()) - if err != nil { - t.Fatalf("failed to create protocol stack: %v", err) - } - defer stack.Close() - - // Register a batch of good services - services := map[string]InstrumentingWrapper{ - "A": InstrumentedServiceMakerA, - "B": InstrumentedServiceMakerB, - "C": InstrumentedServiceMakerC, - } - started := make(map[string]bool) - stopped := make(map[string]bool) - - for id, maker := range services { - id := id // Closure for the constructor - constructor := func(*ServiceContext) (Service, error) { - return &InstrumentedService{ - startHook: func(*p2p.Server) { started[id] = true }, - stopHook: func() { stopped[id] = true }, - }, nil - } - if err := stack.Register(maker(constructor)); err != nil { - t.Fatalf("service %s: registration failed: %v", id, err) - } - } - // Register a service that fails to start - failure := errors.New("fail") - failer := func(*ServiceContext) (Service, error) { - return &InstrumentedService{ - start: failure, - }, nil - } - if err := stack.Register(failer); err != nil { - t.Fatalf("failer registration failed: %v", err) - } // Start the protocol stack and ensure all started services stop for i := 0; i < 100; i++ { if err := stack.Start(); err != failure { t.Fatalf("iter %d: stack startup failure mismatch: have %v, want %v", i, err, failure) } - for id := range services { + for id := range lifecycles { if started[id] && !stopped[id] { t.Fatalf("service %s: started but not stopped", id) } @@ -348,85 +237,89 @@ func TestServiceStartupAbortion(t *testing.T) { } } -// Tests that even if a registered service fails to shut down cleanly, it does +// Tests that even if a registered Lifecycle fails to shut down cleanly, it does // not influence the rest of the shutdown invocations. -func TestServiceTerminationGuarantee(t *testing.T) { +func TestLifecycleTerminationGuarantee(t *testing.T) { stack, err := New(testNodeConfig()) if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } defer stack.Close() - // Register a batch of good services - services := map[string]InstrumentingWrapper{ - "A": InstrumentedServiceMakerA, - "B": InstrumentedServiceMakerB, - "C": InstrumentedServiceMakerC, - } started := make(map[string]bool) stopped := make(map[string]bool) - for id, maker := range services { - id := id // Closure for the constructor - constructor := func(*ServiceContext) (Service, error) { - return &InstrumentedService{ - startHook: func(*p2p.Server) { started[id] = true }, - stopHook: func() { stopped[id] = true }, - }, nil - } - if err := stack.Register(maker(constructor)); err != nil { - t.Fatalf("service %s: registration failed: %v", id, err) - } + // Create a batch of instrumented services + lifecycles := map[string]Lifecycle{ + "A": &InstrumentedServiceA{ + InstrumentedService{ + startHook: func() { started["A"] = true }, + stopHook: func() { stopped["A"] = true }, + }, + }, + "B": &InstrumentedServiceB{ + InstrumentedService{ + startHook: func() { started["B"] = true }, + stopHook: func() { stopped["B"] = true }, + }, + }, + "C": &InstrumentedServiceC{ + InstrumentedService{ + startHook: func() { started["C"] = true }, + stopHook: func() { stopped["C"] = true }, + }, + }, } + // register lifecycles on node + for _, lifecycle := range lifecycles { + stack.RegisterLifecycle(lifecycle) + } + // Register a service that fails to shot down cleanly failure := errors.New("fail") - failer := func(*ServiceContext) (Service, error) { - return &InstrumentedService{ - stop: failure, - }, nil - } - if err := stack.Register(failer); err != nil { - t.Fatalf("failer registration failed: %v", err) + failer := &InstrumentedService{ stop: failure } + stack.RegisterLifecycle(failer) + + // Start the protocol stack, and ensure that a failing shut down terminates all // TODO, deleting loop because constructors no longer stored on node. + // Start the stack and make sure all is online + if err := stack.Start(); err != nil { + t.Fatalf("failed to start protocol stack: %v", err) } - // Start the protocol stack, and ensure that a failing shut down terminates all - for i := 0; i < 100; i++ { - // Start the stack and make sure all is online - if err := stack.Start(); err != nil { - t.Fatalf("iter %d: failed to start protocol stack: %v", i, err) + for id := range lifecycles { + if !started[id] { + t.Fatalf("service %s: service not running", id) } - for id := range services { - if !started[id] { - t.Fatalf("iter %d, service %s: service not running", i, id) - } - if stopped[id] { - t.Fatalf("iter %d, service %s: service already stopped", i, id) - } + if stopped[id] { + t.Fatalf("service %s: service already stopped", id) } - // Stop the stack, verify failure and check all terminations - err := stack.Stop() - if err, ok := err.(*StopError); !ok { - t.Fatalf("iter %d: termination failure mismatch: have %v, want StopError", i, err) - } else { - failer := reflect.TypeOf(&InstrumentedService{}) - if err.Services[failer] != failure { - t.Fatalf("iter %d: failer termination failure mismatch: have %v, want %v", i, err.Services[failer], failure) - } - if len(err.Services) != 1 { - t.Fatalf("iter %d: failure count mismatch: have %d, want %d", i, len(err.Services), 1) - } + } + // Stop the stack, verify failure and check all terminations + err = stack.Stop() + if err, ok := err.(*StopError); !ok { + t.Fatalf("termination failure mismatch: have %v, want StopError", err) + } else { + failer := reflect.TypeOf(&InstrumentedService{}) + if err.Services[failer] != failure { + t.Fatalf("failer termination failure mismatch: have %v, want %v", err.Services[failer], failure) } - for id := range services { - if !stopped[id] { - t.Fatalf("iter %d, service %s: service not terminated", i, id) - } - delete(started, id) - delete(stopped, id) + if len(err.Services) != 1 { + t.Fatalf("failure count mismatch: have %d, want %d", len(err.Services), 1) + } + } + for id := range lifecycles { + if !stopped[id] { + t.Fatalf("service %s: service not terminated", id) } + delete(started, id) + delete(stopped, id) } + + stack.server = &p2p.Server{} + stack.server.PrivateKey = testNodeKey } -// TestServiceRetrieval tests that individual services can be retrieved. -func TestServiceRetrieval(t *testing.T) { +// TestLifecycleRetrieval tests that individual services can be retrieved. +func TestLifecycleRetrieval(t *testing.T) { // Create a simple stack and register two service types stack, err := New(testNodeConfig()) if err != nil { @@ -434,19 +327,22 @@ func TestServiceRetrieval(t *testing.T) { } defer stack.Close() - if err := stack.Register(NewNoopService); err != nil { - t.Fatalf("noop service registration failed: %v", err) - } - if err := stack.Register(NewInstrumentedService); err != nil { - t.Fatalf("instrumented service registration failed: %v", err) + noop := NewNoop() + stack.RegisterLifecycle(noop) + + is, err := NewInstrumentedService() + if err != nil { + t.Fatalf("instrumented service creation failed: %v", err) } + stack.RegisterLifecycle(is) + // Make sure none of the services can be retrieved until started - var noopServ *NoopService - if err := stack.Service(&noopServ); err != ErrNodeStopped { + var noopServ *Noop + if err := stack.Lifecycle(&noopServ); err != ErrNodeStopped { t.Fatalf("noop service retrieval mismatch: have %v, want %v", err, ErrNodeStopped) } var instServ *InstrumentedService - if err := stack.Service(&instServ); err != ErrNodeStopped { + if err := stack.Lifecycle(&instServ); err != ErrNodeStopped { t.Fatalf("instrumented service retrieval mismatch: have %v, want %v", err, ErrNodeStopped) } // Start the stack and ensure everything is retrievable now @@ -455,152 +351,18 @@ func TestServiceRetrieval(t *testing.T) { } defer stack.Stop() - if err := stack.Service(&noopServ); err != nil { + if err := stack.Lifecycle(&noopServ); err != nil { t.Fatalf("noop service retrieval mismatch: have %v, want %v", err, nil) } - if err := stack.Service(&instServ); err != nil { + if err := stack.Lifecycle(&instServ); err != nil { t.Fatalf("instrumented service retrieval mismatch: have %v, want %v", err, nil) } } -// Tests that all protocols defined by individual services get launched. -func TestProtocolGather(t *testing.T) { - stack, err := New(testNodeConfig()) - if err != nil { - t.Fatalf("failed to create protocol stack: %v", err) - } - defer stack.Close() - - // Register a batch of services with some configured number of protocols - services := map[string]struct { - Count int - Maker InstrumentingWrapper - }{ - "zero": {0, InstrumentedServiceMakerA}, - "one": {1, InstrumentedServiceMakerB}, - "many": {10, InstrumentedServiceMakerC}, - } - for id, config := range services { - protocols := make([]p2p.Protocol, config.Count) - for i := 0; i < len(protocols); i++ { - protocols[i].Name = id - protocols[i].Version = uint(i) - } - constructor := func(*ServiceContext) (Service, error) { - return &InstrumentedService{ - protocols: protocols, - }, nil - } - if err := stack.Register(config.Maker(constructor)); err != nil { - t.Fatalf("service %s: registration failed: %v", id, err) - } - } - // Start the services and ensure all protocols start successfully - if err := stack.Start(); err != nil { - t.Fatalf("failed to start protocol stack: %v", err) - } - defer stack.Stop() - - protocols := stack.Server().Protocols - if len(protocols) != 11 { - t.Fatalf("mismatching number of protocols launched: have %d, want %d", len(protocols), 26) - } - for id, config := range services { - for ver := 0; ver < config.Count; ver++ { - launched := false - for i := 0; i < len(protocols); i++ { - if protocols[i].Name == id && protocols[i].Version == uint(ver) { - launched = true - break - } - } - if !launched { - t.Errorf("configured protocol not launched: %s v%d", id, ver) - } - } - } -} - -// Tests that all APIs defined by individual services get exposed. -func TestAPIGather(t *testing.T) { - stack, err := New(testNodeConfig()) - if err != nil { - t.Fatalf("failed to create protocol stack: %v", err) - } - defer stack.Close() - - // Register a batch of services with some configured APIs - calls := make(chan string, 1) - makeAPI := func(result string) *OneMethodAPI { - return &OneMethodAPI{fun: func() { calls <- result }} - } - services := map[string]struct { - APIs []rpc.API - Maker InstrumentingWrapper - }{ - "Zero APIs": { - []rpc.API{}, InstrumentedServiceMakerA}, - "Single API": { - []rpc.API{ - {Namespace: "single", Version: "1", Service: makeAPI("single.v1"), Public: true}, - }, InstrumentedServiceMakerB}, - "Many APIs": { - []rpc.API{ - {Namespace: "multi", Version: "1", Service: makeAPI("multi.v1"), Public: true}, - {Namespace: "multi.v2", Version: "2", Service: makeAPI("multi.v2"), Public: true}, - {Namespace: "multi.v2.nested", Version: "2", Service: makeAPI("multi.v2.nested"), Public: true}, - }, InstrumentedServiceMakerC}, - } - - for id, config := range services { - config := config - constructor := func(*ServiceContext) (Service, error) { - return &InstrumentedService{apis: config.APIs}, nil - } - if err := stack.Register(config.Maker(constructor)); err != nil { - t.Fatalf("service %s: registration failed: %v", id, err) - } - } - // Start the services and ensure all API start successfully - if err := stack.Start(); err != nil { - t.Fatalf("failed to start protocol stack: %v", err) - } - defer stack.Stop() - - // Connect to the RPC server and verify the various registered endpoints - client, err := stack.Attach() - if err != nil { - t.Fatalf("failed to connect to the inproc API server: %v", err) - } - defer client.Close() - - tests := []struct { - Method string - Result string - }{ - {"single_theOneMethod", "single.v1"}, - {"multi_theOneMethod", "multi.v1"}, - {"multi.v2_theOneMethod", "multi.v2"}, - {"multi.v2.nested_theOneMethod", "multi.v2.nested"}, - } - for i, test := range tests { - if err := client.Call(nil, test.Method); err != nil { - t.Errorf("test %d: API request failed: %v", i, err) - } - select { - case result := <-calls: - if result != test.Result { - t.Errorf("test %d: result mismatch: have %s, want %s", i, result, test.Result) - } - case <-time.After(time.Second): - t.Fatalf("test %d: rpc execution timeout", i) - } - } -} - +// Tests whether websocket requests can be handled on the same port as a regular http server func TestWebsocketHTTPOnSamePort_WebsocketRequest(t *testing.T) { node := startHTTP(t) - defer node.stopHTTP() + defer node.Stop() wsReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:7453", nil) if err != nil { @@ -615,9 +377,10 @@ func TestWebsocketHTTPOnSamePort_WebsocketRequest(t *testing.T) { assert.Equal(t, "websocket", resp.Header.Get("Upgrade")) } +// Tests whether http requests can be handled successfully func TestWebsocketHTTPOnSamePort_HTTPRequest(t *testing.T) { node := startHTTP(t) - defer node.stopHTTP() + defer node.Stop() httpReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:7453", nil) if err != nil { @@ -630,13 +393,17 @@ func TestWebsocketHTTPOnSamePort_HTTPRequest(t *testing.T) { } func startHTTP(t *testing.T) *Node { - conf := &Config{HTTPPort: 7453, WSPort: 7453} + conf := &Config{ + HTTPHost: "127.0.0.1", + HTTPPort: 7453, + WSHost: "127.0.0.1", + WSPort: 7453, + } node, err := New(conf) if err != nil { t.Error("could not create a new node ", err) } - - err = node.startHTTP("127.0.0.1:7453", []rpc.API{}, []string{}, []string{}, []string{}, rpc.HTTPTimeouts{}, []string{}) + err = node.Start() if err != nil { t.Error("could not start http service on node ", err) } @@ -648,7 +415,8 @@ func doHTTPRequest(t *testing.T, req *http.Request) *http.Response { client := &http.Client{} resp, err := client.Do(req) if err != nil { - t.Error("could not issue a GET request to the given endpoint", err) + t.Fatal("could not issue a GET request to the given endpoint", err) + } return resp } diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index 040e087dcbbc..ff887738df33 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -10,7 +10,10 @@ import ( ) func TestNewWebsocketUpgradeHandler_websocket(t *testing.T) { - h := &HTTPServer{Srv: rpc.NewServer()} + h := &HTTPServer{ + Srv: rpc.NewServer(), + WSAllowed: true, + } handler := h.NewWebsocketUpgradeHandler(nil, h.Srv.WebsocketHandler([]string{})) ts := httptest.NewServer(handler) defer ts.Close() @@ -27,7 +30,7 @@ func TestNewWebsocketUpgradeHandler_websocket(t *testing.T) { resp, err := client.Do(req) if err != nil { - t.Error("could not issue a GET request to the test http server", err) + t.Fatalf("could not issue a GET request to the test http server %v", err) } responses <- resp }(responses) diff --git a/node/service.go b/node/service.go index c86372bb053e..53ed61017f39 100644 --- a/node/service.go +++ b/node/service.go @@ -18,6 +18,7 @@ package node import ( "path/filepath" + "reflect" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/core/rawdb" @@ -30,6 +31,7 @@ import ( // as well as utility methods to operate on the service environment. type ServiceContext struct { Config Config + Lifecycles map[reflect.Type]Lifecycle // TODO should this be in the service context or should it be on the node itself .. ? EventMux *event.TypeMux // Event multiplexer used for decoupled notifications AccountManager *accounts.Manager // Account manager created by the node. } @@ -71,15 +73,15 @@ func (ctx *ServiceContext) ResolvePath(path string) string { return ctx.Config.ResolvePath(path) } -//// Service retrieves a currently running service registered of a specific type. -//func (ctx *ServiceContext) Service(service interface{}) error { -// element := reflect.ValueOf(service).Elem() -// if running, ok := ctx.services[element.Type()]; ok { -// element.Set(reflect.ValueOf(running)) -// return nil -// } -// return ErrServiceUnknown -//} +// Lifecycle retrieves a currently running lifecycle registered of a specific type. +func (ctx *ServiceContext) Lifecycle(lifecycle interface{}) error { + element := reflect.ValueOf(lifecycle).Elem() + if running, ok := ctx.Lifecycles[element.Type()]; ok { + element.Set(reflect.ValueOf(running)) + return nil + } + return ErrServiceUnknown +} // ExtRPCEnabled returns the indicator whether node enables the external // RPC(http, ws or graphql). diff --git a/node/service_test.go b/node/service_test.go index 5da8e9e434f5..183ca4915330 100644 --- a/node/service_test.go +++ b/node/service_test.go @@ -17,7 +17,6 @@ package node import ( - "fmt" "io/ioutil" "os" "path/filepath" @@ -61,38 +60,42 @@ func TestContextDatabases(t *testing.T) { } } -// Tests that already constructed services can be retrieves by later ones. -func TestContextServices(t *testing.T) { +// Tests that already constructed Lifecycles can be retrieved by later ones. +func TestContextLifecycles(t *testing.T) { stack, err := New(testNodeConfig()) if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } defer stack.Close() // Define a verifier that ensures a NoopA is before it and NoopB after - verifier := func(ctx *ServiceContext) (Service, error) { - var objA *NoopServiceA - if ctx.Service(&objA) != nil { - return nil, fmt.Errorf("former service not found") - } - var objB *NoopServiceB - if err := ctx.Service(&objB); err != ErrServiceUnknown { - return nil, fmt.Errorf("latters lookup error mismatch: have %v, want %v", err, ErrServiceUnknown) - } - return new(NoopService), nil - } - // Register the collection of services - if err := stack.Register(NewNoopServiceA); err != nil { - t.Fatalf("former failed to register service: %v", err) + + noop := NewNoop() + stack.RegisterLifecycle(noop) + + isC, err := NewInstrumentedService() + if err != nil { + t.Fatalf("could not create instrumented service %v", err) } - if err := stack.Register(verifier); err != nil { - t.Fatalf("failed to register service verifier: %v", err) + + isB, err := NewInstrumentedService() + if err != nil { + t.Fatalf("could not create instrumented service %v", err) + } - if err := stack.Register(NewNoopServiceB); err != nil { - t.Fatalf("latter failed to register service: %v", err) + isB.startHook = func() { + if err := stack.ServiceContext.Lifecycle(&noop); err != nil { + t.Errorf("former service not found: %v", err) + } + if err := stack.ServiceContext.Lifecycle(&isC); err != ErrServiceUnknown { + t.Errorf("latters lookup error mismatch: have %v, want %v", err, ErrServiceUnknown) + } } + stack.RegisterLifecycle(isB) + // Start the protocol stack and ensure services are constructed in order if err := stack.Start(); err != nil { t.Fatalf("failed to start stack: %v", err) } + defer stack.Stop() } diff --git a/node/utils_test.go b/node/utils_test.go index 0484ee035708..0755e0411171 100644 --- a/node/utils_test.go +++ b/node/utils_test.go @@ -19,65 +19,47 @@ package node -import ( - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/rpc" -) +import "github.com/ethereum/go-ethereum/p2p" -// NoopService is a trivial implementation of the Service interface. +// NoopLifecycle is a trivial implementation of the Service interface. type NoopLifecycle struct{} func (s *NoopLifecycle) Start() error { return nil } func (s *NoopLifecycle) Stop() error { return nil } -func NewNoop(stack *Node) (*Noop, error) { +func NewNoop() *Noop { noop := new(Noop) - stack.RegisterLifecycle(noop) - return noop, nil + return noop } -// Set of services all wrapping the base NoopService resulting in the same method +// Set of services all wrapping the base NoopLifecycle resulting in the same method // signatures but different outer types. type Noop struct{ NoopLifecycle } -//func NewNoopServiceA(*ServiceContext) (Lifecycle, error) { return new(NoopServiceA), nil } -//func NewNoopServiceB(*ServiceContext) (Lifecycle, error) { return new(NoopServiceB), nil } -//func NewNoopServiceC(*ServiceContext) (Lifecycle, error) { return new(NoopServiceC), nil } -// InstrumentedService is an implementation of Service for which all interface +// InstrumentedService is an implementation of Lifecycle for which all interface // methods can be instrumented both return value as well as event hook wise. type InstrumentedService struct { - protocols []p2p.Protocol - apis []rpc.API start error stop error - server *p2p.Server - - protocolsHook func() - startHook func(*p2p.Server) + startHook func() stopHook func() -} -func NewInstrumentedService(server *p2p.Server) (Lifecycle, error) { - is := &InstrumentedService{ server: server } - return is, nil + protocols []p2p.Protocol } -func (s *InstrumentedService) Protocols() []p2p.Protocol { - if s.protocolsHook != nil { - s.protocolsHook() - } - return s.protocols -} +type InstrumentedServiceA struct { InstrumentedService } +type InstrumentedServiceB struct { InstrumentedService } +type InstrumentedServiceC struct { InstrumentedService } -func (s *InstrumentedService) APIs() []rpc.API { - return s.apis +func NewInstrumentedService() (*InstrumentedService, error) { + return new(InstrumentedService), nil } func (s *InstrumentedService) Start() error { if s.startHook != nil { - s.startHook(s.server) + s.startHook() } return s.start } diff --git a/p2p/server.go b/p2p/server.go index 2e7a9c0b14be..aae88260a91b 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -1120,6 +1120,6 @@ func (srv *Server) PeersInfo() []*PeerInfo { return infos } -func (srv *Server) Listening() bool { - return srv.listener != nil +func (srv *Server) Running() bool { + return srv.running }