123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645 |
- // Copyright 2015 The go-ethereum Authors
- // This file is part of the go-ethereum library.
- //
- // The go-ethereum library is free software: you can redistribute it and/or modify
- // it under the terms of the GNU Lesser General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // The go-ethereum library is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU Lesser General Public License for more details.
- //
- // You should have received a copy of the GNU Lesser General Public License
- // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
- package node
- import (
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "net"
- "net/http"
- "os"
- "reflect"
- "strings"
- "testing"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/plugin"
- "github.com/ethereum/go-ethereum/rpc"
- "github.com/stretchr/testify/assert"
- )
- var (
- testNodeKey, _ = crypto.GenerateKey()
- )
- func testNodeConfig() *Config {
- return &Config{
- Name: "test node",
- P2P: p2p.Config{PrivateKey: testNodeKey},
- }
- }
- // Tests that an empty protocol stack can be closed more than once.
- func TestNodeCloseMultipleTimes(t *testing.T) {
- stack, err := New(testNodeConfig())
- if err != nil {
- t.Fatalf("failed to create protocol stack: %v", err)
- }
- stack.Close()
- // Ensure that a stopped node can be stopped again
- for i := 0; i < 3; i++ {
- if err := stack.Close(); err != ErrNodeStopped {
- t.Fatalf("iter %d: stop failure mismatch: have %v, want %v", i, err, ErrNodeStopped)
- }
- }
- }
- func TestNodeStartMultipleTimes(t *testing.T) {
- stack, err := New(testNodeConfig())
- if err != nil {
- t.Fatalf("failed to create protocol stack: %v", err)
- }
- // Ensure that a node can be successfully started, but only once
- if err := stack.Start(); err != nil {
- t.Fatalf("failed to start node: %v", err)
- }
- if err := stack.Start(); err != ErrNodeRunning {
- t.Fatalf("start failure mismatch: have %v, want %v ", err, ErrNodeRunning)
- }
- // Ensure that a node can be stopped, but only once
- if err := stack.Close(); err != nil {
- t.Fatalf("failed to stop node: %v", err)
- }
- if err := stack.Close(); err != ErrNodeStopped {
- t.Fatalf("stop failure mismatch: have %v, want %v ", err, ErrNodeStopped)
- }
- }
- // Tests that if the data dir is already in use, an appropriate error is returned.
- func TestNodeUsedDataDir(t *testing.T) {
- // Create a temporary folder to use as the data directory
- dir, err := ioutil.TempDir("", "")
- if err != nil {
- t.Fatalf("failed to create temporary data directory: %v", err)
- }
- defer os.RemoveAll(dir)
- // Create a new node based on the data directory
- original, err := New(&Config{DataDir: dir})
- if err != nil {
- t.Fatalf("failed to create original protocol stack: %v", err)
- }
- defer original.Close()
- if err := original.Start(); err != nil {
- t.Fatalf("failed to start original protocol stack: %v", err)
- }
- // Create a second node based on the same data directory and ensure failure
- _, err = New(&Config{DataDir: dir})
- if err != ErrDatadirUsed {
- t.Fatalf("duplicate datadir failure mismatch: have %v, want %v", err, ErrDatadirUsed)
- }
- }
- // Tests whether a Lifecycle can be registered.
- func TestLifecycleRegistry_Successful(t *testing.T) {
- stack, err := New(testNodeConfig())
- if err != nil {
- t.Fatalf("failed to create protocol stack: %v", err)
- }
- defer stack.Close()
- noop := NewNoop()
- stack.RegisterLifecycle(noop)
- if !containsLifecycle(stack.lifecycles, noop) {
- t.Fatalf("lifecycle was not properly registered on the node, %v", err)
- }
- }
- // Tests whether a service's protocols can be registered properly on the node's p2p server.
- func TestRegisterProtocols(t *testing.T) {
- stack, err := New(testNodeConfig())
- if err != nil {
- t.Fatalf("failed to create protocol stack: %v", err)
- }
- defer stack.Close()
- fs, err := NewFullService(stack)
- if err != nil {
- t.Fatalf("could not create full service: %v", err)
- }
- for _, protocol := range fs.Protocols() {
- if !containsProtocol(stack.server.Protocols, protocol) {
- t.Fatalf("protocol %v was not successfully registered", protocol)
- }
- }
- for _, api := range fs.APIs() {
- if !containsAPI(stack.rpcAPIs, api) {
- t.Fatalf("api %v was not successfully registered", api)
- }
- }
- }
- // This test checks that open databases are closed with node.
- func TestNodeCloseClosesDB(t *testing.T) {
- stack, _ := New(testNodeConfig())
- defer stack.Close()
- db, err := stack.OpenDatabase("mydb", 0, 0, "", false)
- if err != nil {
- t.Fatal("can't open DB:", err)
- }
- if err = db.Put([]byte{}, []byte{}); err != nil {
- t.Fatal("can't Put on open DB:", err)
- }
- stack.Close()
- if err = db.Put([]byte{}, []byte{}); err == nil {
- t.Fatal("Put succeeded after node is closed")
- }
- }
- // This test checks that OpenDatabase can be used from within a Lifecycle Start method.
- func TestNodeOpenDatabaseFromLifecycleStart(t *testing.T) {
- stack, _ := New(testNodeConfig())
- defer stack.Close()
- var db ethdb.Database
- var err error
- stack.RegisterLifecycle(&InstrumentedService{
- startHook: func() {
- db, err = stack.OpenDatabase("mydb", 0, 0, "", false)
- if err != nil {
- t.Fatal("can't open DB:", err)
- }
- },
- stopHook: func() {
- db.Close()
- },
- })
- stack.Start()
- stack.Close()
- }
- // This test checks that OpenDatabase can be used from within a Lifecycle Stop method.
- func TestNodeOpenDatabaseFromLifecycleStop(t *testing.T) {
- stack, _ := New(testNodeConfig())
- defer stack.Close()
- stack.RegisterLifecycle(&InstrumentedService{
- stopHook: func() {
- db, err := stack.OpenDatabase("mydb", 0, 0, "", false)
- if err != nil {
- t.Fatal("can't open DB:", err)
- }
- db.Close()
- },
- })
- stack.Start()
- stack.Close()
- }
- // Tests that registered Lifecycles get started and stopped correctly.
- func TestLifecycleLifeCycle(t *testing.T) {
- stack, _ := New(testNodeConfig())
- defer stack.Close()
- started := make(map[string]bool)
- stopped := make(map[string]bool)
- // Create a batch of instrumented services
- lifecycles := map[string]Lifecycle{
- "A": &InstrumentedService{
- startHook: func() { started["A"] = true },
- stopHook: func() { stopped["A"] = true },
- },
- "B": &InstrumentedService{
- startHook: func() { started["B"] = true },
- stopHook: func() { stopped["B"] = true },
- },
- "C": &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 lifecycles {
- if !started[id] {
- t.Fatalf("service %s: freshly started service not running", id)
- }
- if stopped[id] {
- t.Fatalf("service %s: freshly started service already stopped", id)
- }
- }
- // Stop the node and check that all services have been stopped
- if err := stack.Close(); err != nil {
- t.Fatalf("failed to stop protocol stack: %v", err)
- }
- for id := range lifecycles {
- if !stopped[id] {
- t.Fatalf("service %s: freshly terminated service still running", id)
- }
- }
- }
- // Tests that if a Lifecycle fails to start, all others started before it will be
- // shut down.
- func TestLifecycleStartupError(t *testing.T) {
- stack, err := New(testNodeConfig())
- if err != nil {
- t.Fatalf("failed to create protocol stack: %v", err)
- }
- defer stack.Close()
- started := make(map[string]bool)
- stopped := make(map[string]bool)
- // Create a batch of instrumented services
- lifecycles := map[string]Lifecycle{
- "A": &InstrumentedService{
- startHook: func() { started["A"] = true },
- stopHook: func() { stopped["A"] = true },
- },
- "B": &InstrumentedService{
- startHook: func() { started["B"] = true },
- stopHook: func() { stopped["B"] = true },
- },
- "C": &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 construct itself
- failure := errors.New("fail")
- failer := &InstrumentedService{start: failure}
- stack.RegisterLifecycle(failer)
- // Start the protocol stack and ensure all started services stop
- if err := stack.Start(); err != failure {
- t.Fatalf("stack startup failure mismatch: have %v, want %v", err, failure)
- }
- for id := range lifecycles {
- if started[id] && !stopped[id] {
- t.Fatalf("service %s: started but not stopped", id)
- }
- delete(started, id)
- delete(stopped, id)
- }
- }
- // Tests that even if a registered Lifecycle fails to shut down cleanly, it does
- // not influence the rest of the shutdown invocations.
- func TestLifecycleTerminationGuarantee(t *testing.T) {
- stack, err := New(testNodeConfig())
- if err != nil {
- t.Fatalf("failed to create protocol stack: %v", err)
- }
- defer stack.Close()
- started := make(map[string]bool)
- stopped := make(map[string]bool)
- // Create a batch of instrumented services
- lifecycles := map[string]Lifecycle{
- "A": &InstrumentedService{
- startHook: func() { started["A"] = true },
- stopHook: func() { stopped["A"] = true },
- },
- "B": &InstrumentedService{
- startHook: func() { started["B"] = true },
- stopHook: func() { stopped["B"] = true },
- },
- "C": &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 := &InstrumentedService{stop: failure}
- stack.RegisterLifecycle(failer)
- // Start the protocol stack, and ensure that a failing shut down terminates all
- // Start the stack and make sure all is online
- if err := stack.Start(); err != nil {
- t.Fatalf("failed to start protocol stack: %v", err)
- }
- for id := range lifecycles {
- if !started[id] {
- t.Fatalf("service %s: service not running", id)
- }
- if stopped[id] {
- t.Fatalf("service %s: service already stopped", id)
- }
- }
- // Stop the stack, verify failure and check all terminations
- err = stack.Close()
- 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)
- }
- 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
- }
- // Tests whether a handler can be successfully mounted on the canonical HTTP server
- // on the given prefix
- func TestRegisterHandler_Successful(t *testing.T) {
- node := createNode(t, 7878, 7979)
- // create and mount handler
- handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.Write([]byte("success"))
- })
- node.RegisterHandler("test", "/test", handler)
- // start node
- if err := node.Start(); err != nil {
- t.Fatalf("could not start node: %v", err)
- }
- // create HTTP request
- httpReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:7878/test", nil)
- if err != nil {
- t.Error("could not issue new http request ", err)
- }
- // check response
- resp := doHTTPRequest(t, httpReq)
- buf := make([]byte, 7)
- _, err = io.ReadFull(resp.Body, buf)
- if err != nil {
- t.Fatalf("could not read response: %v", err)
- }
- assert.Equal(t, "success", string(buf))
- }
- // Tests that the given handler will not be successfully mounted since no HTTP server
- // is enabled for RPC
- func TestRegisterHandler_Unsuccessful(t *testing.T) {
- node, err := New(&DefaultConfig)
- if err != nil {
- t.Fatalf("could not create new node: %v", err)
- }
- // create and mount handler
- handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.Write([]byte("success"))
- })
- node.RegisterHandler("test", "/test", handler)
- }
- // 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, 0, 0)
- defer node.Close()
- ws := strings.Replace(node.HTTPEndpoint(), "http://", "ws://", 1)
- if node.WSEndpoint() != ws {
- t.Fatalf("endpoints should be the same")
- }
- if !checkRPC(ws) {
- t.Fatalf("ws request failed")
- }
- if !checkRPC(node.HTTPEndpoint()) {
- t.Fatalf("http request failed")
- }
- }
- func TestWebsocketHTTPOnSeparatePort_WSRequest(t *testing.T) {
- // try and get a free port
- listener, err := net.Listen("tcp", "127.0.0.1:0")
- if err != nil {
- t.Fatal("can't listen:", err)
- }
- port := listener.Addr().(*net.TCPAddr).Port
- listener.Close()
- node := startHTTP(t, 0, port)
- defer node.Close()
- wsOnHTTP := strings.Replace(node.HTTPEndpoint(), "http://", "ws://", 1)
- ws := fmt.Sprintf("ws://127.0.0.1:%d", port)
- if node.WSEndpoint() == wsOnHTTP {
- t.Fatalf("endpoints should not be the same")
- }
- // ensure ws endpoint matches the expected endpoint
- if node.WSEndpoint() != ws {
- t.Fatalf("ws endpoint is incorrect: expected %s, got %s", ws, node.WSEndpoint())
- }
- if !checkRPC(ws) {
- t.Fatalf("ws request failed")
- }
- if !checkRPC(node.HTTPEndpoint()) {
- t.Fatalf("http request failed")
- }
- }
- type rpcPrefixTest struct {
- httpPrefix, wsPrefix string
- // These lists paths on which JSON-RPC should be served / not served.
- wantHTTP []string
- wantNoHTTP []string
- wantWS []string
- wantNoWS []string
- }
- func TestNodeRPCPrefix(t *testing.T) {
- t.Parallel()
- tests := []rpcPrefixTest{
- // both off
- {
- httpPrefix: "", wsPrefix: "",
- wantHTTP: []string{"/", "/?p=1"},
- wantNoHTTP: []string{"/test", "/test?p=1"},
- wantWS: []string{"/", "/?p=1"},
- wantNoWS: []string{"/test", "/test?p=1"},
- },
- // only http prefix
- {
- httpPrefix: "/testprefix", wsPrefix: "",
- wantHTTP: []string{"/testprefix", "/testprefix?p=1", "/testprefix/x", "/testprefix/x?p=1"},
- wantNoHTTP: []string{"/", "/?p=1", "/test", "/test?p=1"},
- wantWS: []string{"/", "/?p=1"},
- wantNoWS: []string{"/testprefix", "/testprefix?p=1", "/test", "/test?p=1"},
- },
- // only ws prefix
- {
- httpPrefix: "", wsPrefix: "/testprefix",
- wantHTTP: []string{"/", "/?p=1"},
- wantNoHTTP: []string{"/testprefix", "/testprefix?p=1", "/test", "/test?p=1"},
- wantWS: []string{"/testprefix", "/testprefix?p=1", "/testprefix/x", "/testprefix/x?p=1"},
- wantNoWS: []string{"/", "/?p=1", "/test", "/test?p=1"},
- },
- // both set
- {
- httpPrefix: "/testprefix", wsPrefix: "/testprefix",
- wantHTTP: []string{"/testprefix", "/testprefix?p=1", "/testprefix/x", "/testprefix/x?p=1"},
- wantNoHTTP: []string{"/", "/?p=1", "/test", "/test?p=1"},
- wantWS: []string{"/testprefix", "/testprefix?p=1", "/testprefix/x", "/testprefix/x?p=1"},
- wantNoWS: []string{"/", "/?p=1", "/test", "/test?p=1"},
- },
- }
- for _, test := range tests {
- test := test
- name := fmt.Sprintf("http=%s ws=%s", test.httpPrefix, test.wsPrefix)
- t.Run(name, func(t *testing.T) {
- cfg := &Config{
- HTTPHost: "127.0.0.1",
- HTTPPathPrefix: test.httpPrefix,
- WSHost: "127.0.0.1",
- WSPathPrefix: test.wsPrefix,
- }
- node, err := New(cfg)
- if err != nil {
- t.Fatal("can't create node:", err)
- }
- defer node.Close()
- if err := node.Start(); err != nil {
- t.Fatal("can't start node:", err)
- }
- test.check(t, node)
- })
- }
- }
- func (test rpcPrefixTest) check(t *testing.T, node *Node) {
- t.Helper()
- httpBase := "http://" + node.http.listenAddr()
- wsBase := "ws://" + node.http.listenAddr()
- if node.WSEndpoint() != wsBase+test.wsPrefix {
- t.Errorf("Error: node has wrong WSEndpoint %q", node.WSEndpoint())
- }
- for _, path := range test.wantHTTP {
- resp := rpcRequest(t, httpBase+path)
- if resp.StatusCode != 200 {
- t.Errorf("Error: %s: bad status code %d, want 200", path, resp.StatusCode)
- }
- }
- for _, path := range test.wantNoHTTP {
- resp := rpcRequest(t, httpBase+path)
- if resp.StatusCode != 404 {
- t.Errorf("Error: %s: bad status code %d, want 404", path, resp.StatusCode)
- }
- }
- for _, path := range test.wantWS {
- err := wsRequest(t, wsBase+path, "")
- if err != nil {
- t.Errorf("Error: %s: WebSocket connection failed: %v", path, err)
- }
- }
- for _, path := range test.wantNoWS {
- err := wsRequest(t, wsBase+path, "")
- if err == nil {
- t.Errorf("Error: %s: WebSocket connection succeeded for path in wantNoWS", path)
- }
- }
- }
- func createNode(t *testing.T, httpPort, wsPort int) *Node {
- conf := &Config{
- HTTPHost: "127.0.0.1",
- HTTPPort: httpPort,
- WSHost: "127.0.0.1",
- WSPort: wsPort,
- }
- node, err := New(conf)
- node.pluginManager = plugin.NewEmptyPluginManager()
- if err != nil {
- t.Fatalf("could not create a new node: %v", err)
- }
- return node
- }
- func startHTTP(t *testing.T, httpPort, wsPort int) *Node {
- node := createNode(t, httpPort, wsPort)
- err := node.Start()
- if err != nil {
- t.Fatalf("could not start http service on node: %v", err)
- }
- return node
- }
- func doHTTPRequest(t *testing.T, req *http.Request) *http.Response {
- client := http.DefaultClient
- resp, err := client.Do(req)
- if err != nil {
- t.Fatalf("could not issue a GET request to the given endpoint: %v", err)
- }
- return resp
- }
- func containsProtocol(stackProtocols []p2p.Protocol, protocol p2p.Protocol) bool {
- for _, a := range stackProtocols {
- if reflect.DeepEqual(a, protocol) {
- return true
- }
- }
- return false
- }
- func containsAPI(stackAPIs []rpc.API, api rpc.API) bool {
- for _, a := range stackAPIs {
- if reflect.DeepEqual(a, api) {
- return true
- }
- }
- return false
- }
|