From ba5ec1dc582fa93ca42f5c66567ede69bf23d50e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Isabel=20Sch=C3=B6ps=20Thiel=20=40IsabelSchoepd?=
<155141998+IST-Github@users.noreply.github.com>
Date: Thu, 4 Jan 2024 04:32:57 +0100
Subject: [PATCH] Delete node directory
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Isabel Schöps Thiel @IsabelSchoepd <155141998+IST-Github@users.noreply.github.com>
---
node/api.go | 337 ----------------
node/api_test.go | 355 ----------------
node/config.go | 480 ----------------------
node/config_test.go | 153 -------
node/defaults.go | 133 ------
node/doc.go | 121 ------
node/endpoints.go | 91 -----
node/errors.go | 52 ---
node/jwt_auth.go | 45 ---
node/jwt_handler.go | 80 ----
node/lifecycle.go | 31 --
node/node.go | 822 --------------------------------------
node/node_auth_test.go | 237 -----------
node/node_example_test.go | 59 ---
node/node_test.go | 638 -----------------------------
node/rpcstack.go | 644 -----------------------------
node/rpcstack_test.go | 616 ----------------------------
node/utils_test.go | 106 -----
18 files changed, 5000 deletions(-)
delete mode 100644 node/api.go
delete mode 100644 node/api_test.go
delete mode 100644 node/config.go
delete mode 100644 node/config_test.go
delete mode 100644 node/defaults.go
delete mode 100644 node/doc.go
delete mode 100644 node/endpoints.go
delete mode 100644 node/errors.go
delete mode 100644 node/jwt_auth.go
delete mode 100644 node/jwt_handler.go
delete mode 100644 node/lifecycle.go
delete mode 100644 node/node.go
delete mode 100644 node/node_auth_test.go
delete mode 100644 node/node_example_test.go
delete mode 100644 node/node_test.go
delete mode 100644 node/rpcstack.go
delete mode 100644 node/rpcstack_test.go
delete mode 100644 node/utils_test.go
diff --git a/node/api.go b/node/api.go
deleted file mode 100644
index f81f394beb..0000000000
--- a/node/api.go
+++ /dev/null
@@ -1,337 +0,0 @@
-// 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 .
-
-package node
-
-import (
- "context"
- "fmt"
- "strings"
-
- "github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/internal/debug"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/rpc"
-)
-
-// apis returns the collection of built-in RPC APIs.
-func (n *Node) apis() []rpc.API {
- return []rpc.API{
- {
- Namespace: "admin",
- Service: &adminAPI{n},
- }, {
- Namespace: "debug",
- Service: debug.Handler,
- }, {
- Namespace: "web3",
- Service: &web3API{n},
- },
- }
-}
-
-// adminAPI is the collection of administrative API methods exposed over
-// both secure and unsecure RPC channels.
-type adminAPI struct {
- node *Node // Node interfaced by this API
-}
-
-// AddPeer requests connecting to a remote node, and also maintaining the new
-// connection at all times, even reconnecting if it is lost.
-func (api *adminAPI) AddPeer(url string) (bool, error) {
- // Make sure the server is running, fail otherwise
- server := api.node.Server()
- if server == nil {
- return false, ErrNodeStopped
- }
- // Try to add the url as a static peer and return
- node, err := enode.Parse(enode.ValidSchemes, url)
- if err != nil {
- return false, fmt.Errorf("invalid enode: %v", err)
- }
- server.AddPeer(node)
- return true, nil
-}
-
-// RemovePeer disconnects from a remote node if the connection exists
-func (api *adminAPI) RemovePeer(url string) (bool, error) {
- // Make sure the server is running, fail otherwise
- server := api.node.Server()
- if server == nil {
- return false, ErrNodeStopped
- }
- // Try to remove the url as a static peer and return
- node, err := enode.Parse(enode.ValidSchemes, url)
- if err != nil {
- return false, fmt.Errorf("invalid enode: %v", err)
- }
- server.RemovePeer(node)
- return true, nil
-}
-
-// AddTrustedPeer allows a remote node to always connect, even if slots are full
-func (api *adminAPI) AddTrustedPeer(url string) (bool, error) {
- // Make sure the server is running, fail otherwise
- server := api.node.Server()
- if server == nil {
- return false, ErrNodeStopped
- }
- node, err := enode.Parse(enode.ValidSchemes, url)
- if err != nil {
- return false, fmt.Errorf("invalid enode: %v", err)
- }
- server.AddTrustedPeer(node)
- return true, nil
-}
-
-// RemoveTrustedPeer removes a remote node from the trusted peer set, but it
-// does not disconnect it automatically.
-func (api *adminAPI) RemoveTrustedPeer(url string) (bool, error) {
- // Make sure the server is running, fail otherwise
- server := api.node.Server()
- if server == nil {
- return false, ErrNodeStopped
- }
- node, err := enode.Parse(enode.ValidSchemes, url)
- if err != nil {
- return false, fmt.Errorf("invalid enode: %v", err)
- }
- server.RemoveTrustedPeer(node)
- return true, nil
-}
-
-// PeerEvents creates an RPC subscription which receives peer events from the
-// node's p2p.Server
-func (api *adminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription, error) {
- // Make sure the server is running, fail otherwise
- server := api.node.Server()
- if server == nil {
- return nil, ErrNodeStopped
- }
-
- // Create the subscription
- notifier, supported := rpc.NotifierFromContext(ctx)
- if !supported {
- return nil, rpc.ErrNotificationsUnsupported
- }
- rpcSub := notifier.CreateSubscription()
-
- go func() {
- events := make(chan *p2p.PeerEvent)
- sub := server.SubscribeEvents(events)
- defer sub.Unsubscribe()
-
- for {
- select {
- case event := <-events:
- notifier.Notify(rpcSub.ID, event)
- case <-sub.Err():
- return
- case <-rpcSub.Err():
- return
- case <-notifier.Closed():
- return
- }
- }
- }()
-
- return rpcSub, nil
-}
-
-// StartHTTP starts the HTTP RPC API server.
-func (api *adminAPI) StartHTTP(host *string, port *int, cors *string, apis *string, vhosts *string) (bool, error) {
- api.node.lock.Lock()
- defer api.node.lock.Unlock()
-
- // Determine host and port.
- if host == nil {
- h := DefaultHTTPHost
- if api.node.config.HTTPHost != "" {
- h = api.node.config.HTTPHost
- }
- host = &h
- }
- if port == nil {
- port = &api.node.config.HTTPPort
- }
-
- // Determine config.
- config := httpConfig{
- CorsAllowedOrigins: api.node.config.HTTPCors,
- Vhosts: api.node.config.HTTPVirtualHosts,
- Modules: api.node.config.HTTPModules,
- rpcEndpointConfig: rpcEndpointConfig{
- batchItemLimit: api.node.config.BatchRequestLimit,
- batchResponseSizeLimit: api.node.config.BatchResponseMaxSize,
- },
- }
- if cors != nil {
- config.CorsAllowedOrigins = nil
- for _, origin := range strings.Split(*cors, ",") {
- config.CorsAllowedOrigins = append(config.CorsAllowedOrigins, strings.TrimSpace(origin))
- }
- }
- if vhosts != nil {
- config.Vhosts = nil
- for _, vhost := range strings.Split(*host, ",") {
- config.Vhosts = append(config.Vhosts, strings.TrimSpace(vhost))
- }
- }
- if apis != nil {
- config.Modules = nil
- for _, m := range strings.Split(*apis, ",") {
- config.Modules = append(config.Modules, strings.TrimSpace(m))
- }
- }
-
- if err := api.node.http.setListenAddr(*host, *port); err != nil {
- return false, err
- }
- if err := api.node.http.enableRPC(api.node.rpcAPIs, config); err != nil {
- return false, err
- }
- if err := api.node.http.start(); err != nil {
- return false, err
- }
- return true, nil
-}
-
-// StartRPC starts the HTTP RPC API server.
-// Deprecated: use StartHTTP instead.
-func (api *adminAPI) StartRPC(host *string, port *int, cors *string, apis *string, vhosts *string) (bool, error) {
- log.Warn("Deprecation warning", "method", "admin.StartRPC", "use-instead", "admin.StartHTTP")
- return api.StartHTTP(host, port, cors, apis, vhosts)
-}
-
-// StopHTTP shuts down the HTTP server.
-func (api *adminAPI) StopHTTP() (bool, error) {
- api.node.http.stop()
- return true, nil
-}
-
-// StopRPC shuts down the HTTP server.
-// Deprecated: use StopHTTP instead.
-func (api *adminAPI) StopRPC() (bool, error) {
- log.Warn("Deprecation warning", "method", "admin.StopRPC", "use-instead", "admin.StopHTTP")
- return api.StopHTTP()
-}
-
-// StartWS starts the websocket RPC API server.
-func (api *adminAPI) StartWS(host *string, port *int, allowedOrigins *string, apis *string) (bool, error) {
- api.node.lock.Lock()
- defer api.node.lock.Unlock()
-
- // Determine host and port.
- if host == nil {
- h := DefaultWSHost
- if api.node.config.WSHost != "" {
- h = api.node.config.WSHost
- }
- host = &h
- }
- if port == nil {
- port = &api.node.config.WSPort
- }
-
- // Determine config.
- config := wsConfig{
- Modules: api.node.config.WSModules,
- Origins: api.node.config.WSOrigins,
- // ExposeAll: api.node.config.WSExposeAll,
- rpcEndpointConfig: rpcEndpointConfig{
- batchItemLimit: api.node.config.BatchRequestLimit,
- batchResponseSizeLimit: api.node.config.BatchResponseMaxSize,
- },
- }
- if apis != nil {
- config.Modules = nil
- for _, m := range strings.Split(*apis, ",") {
- config.Modules = append(config.Modules, strings.TrimSpace(m))
- }
- }
- if allowedOrigins != nil {
- config.Origins = nil
- for _, origin := range strings.Split(*allowedOrigins, ",") {
- config.Origins = append(config.Origins, strings.TrimSpace(origin))
- }
- }
-
- // Enable WebSocket on the server.
- server := api.node.wsServerForPort(*port, false)
- if err := server.setListenAddr(*host, *port); err != nil {
- return false, err
- }
- openApis, _ := api.node.getAPIs()
- if err := server.enableWS(openApis, config); err != nil {
- return false, err
- }
- if err := server.start(); err != nil {
- return false, err
- }
- api.node.http.log.Info("WebSocket endpoint opened", "url", api.node.WSEndpoint())
- return true, nil
-}
-
-// StopWS terminates all WebSocket servers.
-func (api *adminAPI) StopWS() (bool, error) {
- api.node.http.stopWS()
- api.node.ws.stop()
- return true, nil
-}
-
-// Peers retrieves all the information we know about each individual peer at the
-// protocol granularity.
-func (api *adminAPI) Peers() ([]*p2p.PeerInfo, error) {
- server := api.node.Server()
- if server == nil {
- return nil, ErrNodeStopped
- }
- return server.PeersInfo(), nil
-}
-
-// NodeInfo retrieves all the information we know about the host node at the
-// protocol granularity.
-func (api *adminAPI) NodeInfo() (*p2p.NodeInfo, error) {
- server := api.node.Server()
- if server == nil {
- return nil, ErrNodeStopped
- }
- return server.NodeInfo(), nil
-}
-
-// Datadir retrieves the current data directory the node is using.
-func (api *adminAPI) Datadir() string {
- return api.node.DataDir()
-}
-
-// web3API offers helper utils
-type web3API struct {
- stack *Node
-}
-
-// ClientVersion returns the node name
-func (s *web3API) ClientVersion() string {
- return s.stack.Server().Name
-}
-
-// Sha3 applies the ethereum sha3 implementation on the input.
-// It assumes the input is hex encoded.
-func (s *web3API) Sha3(input hexutil.Bytes) hexutil.Bytes {
- return crypto.Keccak256(input)
-}
diff --git a/node/api_test.go b/node/api_test.go
deleted file mode 100644
index 8761c4883e..0000000000
--- a/node/api_test.go
+++ /dev/null
@@ -1,355 +0,0 @@
-// Copyright 2020 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 .
-
-package node
-
-import (
- "bytes"
- "io"
- "net"
- "net/http"
- "net/url"
- "strings"
- "testing"
-
- "github.com/ethereum/go-ethereum/rpc"
- "github.com/stretchr/testify/assert"
-)
-
-// This test uses the admin_startRPC and admin_startWS APIs,
-// checking whether the HTTP server is started correctly.
-func TestStartRPC(t *testing.T) {
- type test struct {
- name string
- cfg Config
- fn func(*testing.T, *Node, *adminAPI)
-
- // Checks. These run after the node is configured and all API calls have been made.
- wantReachable bool // whether the HTTP server should be reachable at all
- wantHandlers bool // whether RegisterHandler handlers should be accessible
- wantRPC bool // whether JSON-RPC/HTTP should be accessible
- wantWS bool // whether JSON-RPC/WS should be accessible
- }
-
- tests := []test{
- {
- name: "all off",
- cfg: Config{},
- fn: func(t *testing.T, n *Node, api *adminAPI) {
- },
- wantReachable: false,
- wantHandlers: false,
- wantRPC: false,
- wantWS: false,
- },
- {
- name: "rpc enabled through config",
- cfg: Config{HTTPHost: "127.0.0.1"},
- fn: func(t *testing.T, n *Node, api *adminAPI) {
- },
- wantReachable: true,
- wantHandlers: true,
- wantRPC: true,
- wantWS: false,
- },
- {
- name: "rpc enabled through API",
- cfg: Config{},
- fn: func(t *testing.T, n *Node, api *adminAPI) {
- _, err := api.StartHTTP(sp("127.0.0.1"), ip(0), nil, nil, nil)
- assert.NoError(t, err)
- },
- wantReachable: true,
- wantHandlers: true,
- wantRPC: true,
- wantWS: false,
- },
- {
- name: "rpc start again after failure",
- cfg: Config{},
- fn: func(t *testing.T, n *Node, api *adminAPI) {
- // Listen on a random port.
- listener, err := net.Listen("tcp", "127.0.0.1:0")
- if err != nil {
- t.Fatal("can't listen:", err)
- }
- defer listener.Close()
- port := listener.Addr().(*net.TCPAddr).Port
-
- // Now try to start RPC on that port. This should fail.
- _, err = api.StartHTTP(sp("127.0.0.1"), ip(port), nil, nil, nil)
- if err == nil {
- t.Fatal("StartHTTP should have failed on port", port)
- }
-
- // Try again after unblocking the port. It should work this time.
- listener.Close()
- _, err = api.StartHTTP(sp("127.0.0.1"), ip(port), nil, nil, nil)
- assert.NoError(t, err)
- },
- wantReachable: true,
- wantHandlers: true,
- wantRPC: true,
- wantWS: false,
- },
- {
- name: "rpc stopped through API",
- cfg: Config{HTTPHost: "127.0.0.1"},
- fn: func(t *testing.T, n *Node, api *adminAPI) {
- _, err := api.StopHTTP()
- assert.NoError(t, err)
- },
- wantReachable: false,
- wantHandlers: false,
- wantRPC: false,
- wantWS: false,
- },
- {
- name: "rpc stopped twice",
- cfg: Config{HTTPHost: "127.0.0.1"},
- fn: func(t *testing.T, n *Node, api *adminAPI) {
- _, err := api.StopHTTP()
- assert.NoError(t, err)
-
- _, err = api.StopHTTP()
- assert.NoError(t, err)
- },
- wantReachable: false,
- wantHandlers: false,
- wantRPC: false,
- wantWS: false,
- },
- {
- name: "ws enabled through config",
- cfg: Config{WSHost: "127.0.0.1"},
- wantReachable: true,
- wantHandlers: false,
- wantRPC: false,
- wantWS: true,
- },
- {
- name: "ws enabled through API",
- cfg: Config{},
- fn: func(t *testing.T, n *Node, api *adminAPI) {
- _, err := api.StartWS(sp("127.0.0.1"), ip(0), nil, nil)
- assert.NoError(t, err)
- },
- wantReachable: true,
- wantHandlers: false,
- wantRPC: false,
- wantWS: true,
- },
- {
- name: "ws stopped through API",
- cfg: Config{WSHost: "127.0.0.1"},
- fn: func(t *testing.T, n *Node, api *adminAPI) {
- _, err := api.StopWS()
- assert.NoError(t, err)
- },
- wantReachable: false,
- wantHandlers: false,
- wantRPC: false,
- wantWS: false,
- },
- {
- name: "ws stopped twice",
- cfg: Config{WSHost: "127.0.0.1"},
- fn: func(t *testing.T, n *Node, api *adminAPI) {
- _, err := api.StopWS()
- assert.NoError(t, err)
-
- _, err = api.StopWS()
- assert.NoError(t, err)
- },
- wantReachable: false,
- wantHandlers: false,
- wantRPC: false,
- wantWS: false,
- },
- {
- name: "ws enabled after RPC",
- cfg: Config{HTTPHost: "127.0.0.1"},
- fn: func(t *testing.T, n *Node, api *adminAPI) {
- wsport := n.http.port
- _, err := api.StartWS(sp("127.0.0.1"), ip(wsport), nil, nil)
- assert.NoError(t, err)
- },
- wantReachable: true,
- wantHandlers: true,
- wantRPC: true,
- wantWS: true,
- },
- {
- name: "ws enabled after RPC then stopped",
- cfg: Config{HTTPHost: "127.0.0.1"},
- fn: func(t *testing.T, n *Node, api *adminAPI) {
- wsport := n.http.port
- _, err := api.StartWS(sp("127.0.0.1"), ip(wsport), nil, nil)
- assert.NoError(t, err)
-
- _, err = api.StopWS()
- assert.NoError(t, err)
- },
- wantReachable: true,
- wantHandlers: true,
- wantRPC: true,
- wantWS: false,
- },
- {
- name: "rpc stopped with ws enabled",
- fn: func(t *testing.T, n *Node, api *adminAPI) {
- _, err := api.StartHTTP(sp("127.0.0.1"), ip(0), nil, nil, nil)
- assert.NoError(t, err)
-
- wsport := n.http.port
- _, err = api.StartWS(sp("127.0.0.1"), ip(wsport), nil, nil)
- assert.NoError(t, err)
-
- _, err = api.StopHTTP()
- assert.NoError(t, err)
- },
- wantReachable: false,
- wantHandlers: false,
- wantRPC: false,
- wantWS: false,
- },
- {
- name: "rpc enabled after ws",
- fn: func(t *testing.T, n *Node, api *adminAPI) {
- _, err := api.StartWS(sp("127.0.0.1"), ip(0), nil, nil)
- assert.NoError(t, err)
-
- wsport := n.http.port
- _, err = api.StartHTTP(sp("127.0.0.1"), ip(wsport), nil, nil, nil)
- assert.NoError(t, err)
- },
- wantReachable: true,
- wantHandlers: true,
- wantRPC: true,
- wantWS: true,
- },
- }
-
- for _, test := range tests {
- test := test
- t.Run(test.name, func(t *testing.T) {
- t.Parallel()
-
- // Apply some sane defaults.
- config := test.cfg
- // config.Logger = testlog.Logger(t, log.LvlDebug)
- config.P2P.NoDiscovery = true
- if config.HTTPTimeouts == (rpc.HTTPTimeouts{}) {
- config.HTTPTimeouts = rpc.DefaultHTTPTimeouts
- }
-
- // Create Node.
- stack, err := New(&config)
- if err != nil {
- t.Fatal("can't create node:", err)
- }
- defer stack.Close()
-
- // Register the test handler.
- stack.RegisterHandler("test", "/test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.Write([]byte("OK"))
- }))
-
- if err := stack.Start(); err != nil {
- t.Fatal("can't start node:", err)
- }
-
- // Run the API call hook.
- if test.fn != nil {
- test.fn(t, stack, &adminAPI{stack})
- }
-
- // Check if the HTTP endpoints are available.
- baseURL := stack.HTTPEndpoint()
- reachable := checkReachable(baseURL)
- handlersAvailable := checkBodyOK(baseURL + "/test")
- rpcAvailable := checkRPC(baseURL)
- wsAvailable := checkRPC(strings.Replace(baseURL, "http://", "ws://", 1))
- if reachable != test.wantReachable {
- t.Errorf("HTTP server is %sreachable, want it %sreachable", not(reachable), not(test.wantReachable))
- }
- if handlersAvailable != test.wantHandlers {
- t.Errorf("RegisterHandler handlers %savailable, want them %savailable", not(handlersAvailable), not(test.wantHandlers))
- }
- if rpcAvailable != test.wantRPC {
- t.Errorf("HTTP RPC %savailable, want it %savailable", not(rpcAvailable), not(test.wantRPC))
- }
- if wsAvailable != test.wantWS {
- t.Errorf("WS RPC %savailable, want it %savailable", not(wsAvailable), not(test.wantWS))
- }
- })
- }
-}
-
-// checkReachable checks if the TCP endpoint in rawurl is open.
-func checkReachable(rawurl string) bool {
- u, err := url.Parse(rawurl)
- if err != nil {
- panic(err)
- }
- conn, err := net.Dial("tcp", u.Host)
- if err != nil {
- return false
- }
- conn.Close()
- return true
-}
-
-// checkBodyOK checks whether the given HTTP URL responds with 200 OK and body "OK".
-func checkBodyOK(url string) bool {
- resp, err := http.Get(url)
- if err != nil {
- return false
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != 200 {
- return false
- }
- buf := make([]byte, 2)
- if _, err = io.ReadFull(resp.Body, buf); err != nil {
- return false
- }
- return bytes.Equal(buf, []byte("OK"))
-}
-
-// checkRPC checks whether JSON-RPC works against the given URL.
-func checkRPC(url string) bool {
- c, err := rpc.Dial(url)
- if err != nil {
- return false
- }
- defer c.Close()
-
- _, err = c.SupportedModules()
- return err == nil
-}
-
-// string/int pointer helpers.
-func sp(s string) *string { return &s }
-func ip(i int) *int { return &i }
-
-func not(ok bool) string {
- if ok {
- return ""
- }
- return "not "
-}
diff --git a/node/config.go b/node/config.go
deleted file mode 100644
index 949db887e4..0000000000
--- a/node/config.go
+++ /dev/null
@@ -1,480 +0,0 @@
-// Copyright 2014 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 .
-
-package node
-
-import (
- "crypto/ecdsa"
- "fmt"
- "net"
- "os"
- "path/filepath"
- "runtime"
- "strings"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/rpc"
-)
-
-const (
- datadirPrivateKey = "nodekey" // Path within the datadir to the node's private key
- datadirJWTKey = "jwtsecret" // Path within the datadir to the node's jwt secret
- datadirDefaultKeyStore = "keystore" // Path within the datadir to the keystore
- datadirStaticNodes = "static-nodes.json" // Path within the datadir to the static node list
- datadirTrustedNodes = "trusted-nodes.json" // Path within the datadir to the trusted node list
- datadirNodeDatabase = "nodes" // Path within the datadir to store the node infos
-)
-
-// Config represents a small collection of configuration values to fine tune the
-// P2P network layer of a protocol stack. These values can be further extended by
-// all registered services.
-type Config struct {
- // Name sets the instance name of the node. It must not contain the / character and is
- // used in the devp2p node identifier. The instance name of geth is "geth". If no
- // value is specified, the basename of the current executable is used.
- Name string `toml:"-"`
-
- // UserIdent, if set, is used as an additional component in the devp2p node identifier.
- UserIdent string `toml:",omitempty"`
-
- // Version should be set to the version number of the program. It is used
- // in the devp2p node identifier.
- Version string `toml:"-"`
-
- // DataDir is the file system folder the node should use for any data storage
- // requirements. The configured data directory will not be directly shared with
- // registered services, instead those can use utility methods to create/access
- // databases or flat files. This enables ephemeral nodes which can fully reside
- // in memory.
- DataDir string
-
- // Configuration of peer-to-peer networking.
- P2P p2p.Config
-
- // KeyStoreDir is the file system folder that contains private keys. The directory can
- // be specified as a relative path, in which case it is resolved relative to the
- // current directory.
- //
- // If KeyStoreDir is empty, the default location is the "keystore" subdirectory of
- // DataDir. If DataDir is unspecified and KeyStoreDir is empty, an ephemeral directory
- // is created by New and destroyed when the node is stopped.
- KeyStoreDir string `toml:",omitempty"`
-
- // ExternalSigner specifies an external URI for a clef-type signer.
- ExternalSigner string `toml:",omitempty"`
-
- // UseLightweightKDF lowers the memory and CPU requirements of the key store
- // scrypt KDF at the expense of security.
- UseLightweightKDF bool `toml:",omitempty"`
-
- // InsecureUnlockAllowed allows user to unlock accounts in unsafe http environment.
- InsecureUnlockAllowed bool `toml:",omitempty"`
-
- // NoUSB disables hardware wallet monitoring and connectivity.
- // Deprecated: USB monitoring is disabled by default and must be enabled explicitly.
- NoUSB bool `toml:",omitempty"`
-
- // USB enables hardware wallet monitoring and connectivity.
- USB bool `toml:",omitempty"`
-
- // SmartCardDaemonPath is the path to the smartcard daemon's socket.
- SmartCardDaemonPath string `toml:",omitempty"`
-
- // IPCPath is the requested location to place the IPC endpoint. If the path is
- // a simple file name, it is placed inside the data directory (or on the root
- // pipe path on Windows), whereas if it's a resolvable path name (absolute or
- // relative), then that specific path is enforced. An empty path disables IPC.
- IPCPath string
-
- // HTTPHost is the host interface on which to start the HTTP RPC server. If this
- // field is empty, no HTTP API endpoint will be started.
- HTTPHost string
-
- // HTTPPort is the TCP port number on which to start the HTTP RPC server. The
- // default zero value is/ valid and will pick a port number randomly (useful
- // for ephemeral nodes).
- HTTPPort int `toml:",omitempty"`
-
- // HTTPCors is the Cross-Origin Resource Sharing header to send to requesting
- // clients. Please be aware that CORS is a browser enforced security, it's fully
- // useless for custom HTTP clients.
- HTTPCors []string `toml:",omitempty"`
-
- // HTTPVirtualHosts is the list of virtual hostnames which are allowed on incoming requests.
- // This is by default {'localhost'}. Using this prevents attacks like
- // DNS rebinding, which bypasses SOP by simply masquerading as being within the same
- // origin. These attacks do not utilize CORS, since they are not cross-domain.
- // By explicitly checking the Host-header, the server will not allow requests
- // made against the server with a malicious host domain.
- // Requests using ip address directly are not affected
- HTTPVirtualHosts []string `toml:",omitempty"`
-
- // HTTPModules is a list of API modules to expose via the HTTP RPC interface.
- // If the module list is empty, all RPC API endpoints designated public will be
- // exposed.
- HTTPModules []string
-
- // HTTPTimeouts allows for customization of the timeout values used by the HTTP RPC
- // interface.
- HTTPTimeouts rpc.HTTPTimeouts
-
- // HTTPPathPrefix specifies a path prefix on which http-rpc is to be served.
- HTTPPathPrefix string `toml:",omitempty"`
-
- // AuthAddr is the listening address on which authenticated APIs are provided.
- AuthAddr string `toml:",omitempty"`
-
- // AuthPort is the port number on which authenticated APIs are provided.
- AuthPort int `toml:",omitempty"`
-
- // AuthVirtualHosts is the list of virtual hostnames which are allowed on incoming requests
- // for the authenticated api. This is by default {'localhost'}.
- AuthVirtualHosts []string `toml:",omitempty"`
-
- // WSHost is the host interface on which to start the websocket RPC server. If
- // this field is empty, no websocket API endpoint will be started.
- WSHost string
-
- // WSPort is the TCP port number on which to start the websocket RPC server. The
- // default zero value is/ valid and will pick a port number randomly (useful for
- // ephemeral nodes).
- WSPort int `toml:",omitempty"`
-
- // WSPathPrefix specifies a path prefix on which ws-rpc is to be served.
- WSPathPrefix string `toml:",omitempty"`
-
- // WSOrigins is the list of domain to accept websocket requests from. Please be
- // aware that the server can only act upon the HTTP request the client sends and
- // cannot verify the validity of the request header.
- WSOrigins []string `toml:",omitempty"`
-
- // WSModules is a list of API modules to expose via the websocket RPC interface.
- // If the module list is empty, all RPC API endpoints designated public will be
- // exposed.
- WSModules []string
-
- // WSExposeAll exposes all API modules via the WebSocket RPC interface rather
- // than just the public ones.
- //
- // *WARNING* Only set this if the node is running in a trusted network, exposing
- // private APIs to untrusted users is a major security risk.
- WSExposeAll bool `toml:",omitempty"`
-
- // GraphQLCors is the Cross-Origin Resource Sharing header to send to requesting
- // clients. Please be aware that CORS is a browser enforced security, it's fully
- // useless for custom HTTP clients.
- GraphQLCors []string `toml:",omitempty"`
-
- // GraphQLVirtualHosts is the list of virtual hostnames which are allowed on incoming requests.
- // This is by default {'localhost'}. Using this prevents attacks like
- // DNS rebinding, which bypasses SOP by simply masquerading as being within the same
- // origin. These attacks do not utilize CORS, since they are not cross-domain.
- // By explicitly checking the Host-header, the server will not allow requests
- // made against the server with a malicious host domain.
- // Requests using ip address directly are not affected
- GraphQLVirtualHosts []string `toml:",omitempty"`
-
- // Logger is a custom logger to use with the p2p.Server.
- Logger log.Logger `toml:",omitempty"`
-
- oldGethResourceWarning bool
-
- // AllowUnprotectedTxs allows non EIP-155 protected transactions to be send over RPC.
- AllowUnprotectedTxs bool `toml:",omitempty"`
-
- // BatchRequestLimit is the maximum number of requests in a batch.
- BatchRequestLimit int `toml:",omitempty"`
-
- // BatchResponseMaxSize is the maximum number of bytes returned from a batched rpc call.
- BatchResponseMaxSize int `toml:",omitempty"`
-
- // JWTSecret is the path to the hex-encoded jwt secret.
- JWTSecret string `toml:",omitempty"`
-
- // EnablePersonal enables the deprecated personal namespace.
- EnablePersonal bool `toml:"-"`
-
- DBEngine string `toml:",omitempty"`
-}
-
-// IPCEndpoint resolves an IPC endpoint based on a configured value, taking into
-// account the set data folders as well as the designated platform we're currently
-// running on.
-func (c *Config) IPCEndpoint() string {
- // Short circuit if IPC has not been enabled
- if c.IPCPath == "" {
- return ""
- }
- // On windows we can only use plain top-level pipes
- if runtime.GOOS == "windows" {
- if strings.HasPrefix(c.IPCPath, `\\.\pipe\`) {
- return c.IPCPath
- }
- return `\\.\pipe\` + c.IPCPath
- }
- // Resolve names into the data directory full paths otherwise
- if filepath.Base(c.IPCPath) == c.IPCPath {
- if c.DataDir == "" {
- return filepath.Join(os.TempDir(), c.IPCPath)
- }
- return filepath.Join(c.DataDir, c.IPCPath)
- }
- return c.IPCPath
-}
-
-// NodeDB returns the path to the discovery node database.
-func (c *Config) NodeDB() string {
- if c.DataDir == "" {
- return "" // ephemeral
- }
- return c.ResolvePath(datadirNodeDatabase)
-}
-
-// DefaultIPCEndpoint returns the IPC path used by default.
-func DefaultIPCEndpoint(clientIdentifier string) string {
- if clientIdentifier == "" {
- clientIdentifier = strings.TrimSuffix(filepath.Base(os.Args[0]), ".exe")
- if clientIdentifier == "" {
- panic("empty executable name")
- }
- }
- config := &Config{DataDir: DefaultDataDir(), IPCPath: clientIdentifier + ".ipc"}
- return config.IPCEndpoint()
-}
-
-// HTTPEndpoint resolves an HTTP endpoint based on the configured host interface
-// and port parameters.
-func (c *Config) HTTPEndpoint() string {
- if c.HTTPHost == "" {
- return ""
- }
- return net.JoinHostPort(c.HTTPHost, fmt.Sprintf("%d", c.HTTPPort))
-}
-
-// DefaultHTTPEndpoint returns the HTTP endpoint used by default.
-func DefaultHTTPEndpoint() string {
- config := &Config{HTTPHost: DefaultHTTPHost, HTTPPort: DefaultHTTPPort, AuthPort: DefaultAuthPort}
- return config.HTTPEndpoint()
-}
-
-// WSEndpoint resolves a websocket endpoint based on the configured host interface
-// and port parameters.
-func (c *Config) WSEndpoint() string {
- if c.WSHost == "" {
- return ""
- }
- return net.JoinHostPort(c.WSHost, fmt.Sprintf("%d", c.WSPort))
-}
-
-// DefaultWSEndpoint returns the websocket endpoint used by default.
-func DefaultWSEndpoint() string {
- config := &Config{WSHost: DefaultWSHost, WSPort: DefaultWSPort}
- return config.WSEndpoint()
-}
-
-// ExtRPCEnabled returns the indicator whether node enables the external
-// RPC(http, ws or graphql).
-func (c *Config) ExtRPCEnabled() bool {
- return c.HTTPHost != "" || c.WSHost != ""
-}
-
-// NodeName returns the devp2p node identifier.
-func (c *Config) NodeName() string {
- name := c.name()
- // Backwards compatibility: previous versions used title-cased "Geth", keep that.
- if name == "geth" || name == "geth-testnet" {
- name = "Geth"
- }
- if c.UserIdent != "" {
- name += "/" + c.UserIdent
- }
- if c.Version != "" {
- name += "/v" + c.Version
- }
- name += "/" + runtime.GOOS + "-" + runtime.GOARCH
- name += "/" + runtime.Version()
- return name
-}
-
-func (c *Config) name() string {
- if c.Name == "" {
- progname := strings.TrimSuffix(filepath.Base(os.Args[0]), ".exe")
- if progname == "" {
- panic("empty executable name, set Config.Name")
- }
- return progname
- }
- return c.Name
-}
-
-// These resources are resolved differently for "geth" instances.
-var isOldGethResource = map[string]bool{
- "chaindata": true,
- "nodes": true,
- "nodekey": true,
- "static-nodes.json": false, // no warning for these because they have their
- "trusted-nodes.json": false, // own separate warning.
-}
-
-// ResolvePath resolves path in the instance directory.
-func (c *Config) ResolvePath(path string) string {
- if filepath.IsAbs(path) {
- return path
- }
- if c.DataDir == "" {
- return ""
- }
- // Backwards-compatibility: ensure that data directory files created
- // by geth 1.4 are used if they exist.
- if warn, isOld := isOldGethResource[path]; isOld {
- oldpath := ""
- if c.name() == "geth" {
- oldpath = filepath.Join(c.DataDir, path)
- }
- if oldpath != "" && common.FileExist(oldpath) {
- if warn && !c.oldGethResourceWarning {
- c.oldGethResourceWarning = true
- log.Warn("Using deprecated resource file, please move this file to the 'geth' subdirectory of datadir.", "file", oldpath)
- }
- return oldpath
- }
- }
- return filepath.Join(c.instanceDir(), path)
-}
-
-func (c *Config) instanceDir() string {
- if c.DataDir == "" {
- return ""
- }
- return filepath.Join(c.DataDir, c.name())
-}
-
-// NodeKey retrieves the currently configured private key of the node, checking
-// first any manually set key, falling back to the one found in the configured
-// data folder. If no key can be found, a new one is generated.
-func (c *Config) NodeKey() *ecdsa.PrivateKey {
- // Use any specifically configured key.
- if c.P2P.PrivateKey != nil {
- return c.P2P.PrivateKey
- }
- // Generate ephemeral key if no datadir is being used.
- if c.DataDir == "" {
- key, err := crypto.GenerateKey()
- if err != nil {
- log.Crit(fmt.Sprintf("Failed to generate ephemeral node key: %v", err))
- }
- return key
- }
-
- keyfile := c.ResolvePath(datadirPrivateKey)
- if key, err := crypto.LoadECDSA(keyfile); err == nil {
- return key
- }
- // No persistent key found, generate and store a new one.
- key, err := crypto.GenerateKey()
- if err != nil {
- log.Crit(fmt.Sprintf("Failed to generate node key: %v", err))
- }
- instanceDir := filepath.Join(c.DataDir, c.name())
- if err := os.MkdirAll(instanceDir, 0700); err != nil {
- log.Error(fmt.Sprintf("Failed to persist node key: %v", err))
- return key
- }
- keyfile = filepath.Join(instanceDir, datadirPrivateKey)
- if err := crypto.SaveECDSA(keyfile, key); err != nil {
- log.Error(fmt.Sprintf("Failed to persist node key: %v", err))
- }
- return key
-}
-
-// checkLegacyFiles inspects the datadir for signs of legacy static-nodes
-// and trusted-nodes files. If they exist it raises an error.
-func (c *Config) checkLegacyFiles() {
- c.checkLegacyFile(c.ResolvePath(datadirStaticNodes))
- c.checkLegacyFile(c.ResolvePath(datadirTrustedNodes))
-}
-
-// checkLegacyFile will only raise an error if a file at the given path exists.
-func (c *Config) checkLegacyFile(path string) {
- // Short circuit if no node config is present
- if c.DataDir == "" {
- return
- }
- if _, err := os.Stat(path); err != nil {
- return
- }
- logger := c.Logger
- if logger == nil {
- logger = log.Root()
- }
- switch fname := filepath.Base(path); fname {
- case "static-nodes.json":
- logger.Error("The static-nodes.json file is deprecated and ignored. Use P2P.StaticNodes in config.toml instead.")
- case "trusted-nodes.json":
- logger.Error("The trusted-nodes.json file is deprecated and ignored. Use P2P.TrustedNodes in config.toml instead.")
- default:
- // We shouldn't wind up here, but better print something just in case.
- logger.Error("Ignoring deprecated file.", "file", path)
- }
-}
-
-// KeyDirConfig determines the settings for keydirectory
-func (c *Config) KeyDirConfig() (string, error) {
- var (
- keydir string
- err error
- )
- switch {
- case filepath.IsAbs(c.KeyStoreDir):
- keydir = c.KeyStoreDir
- case c.DataDir != "":
- if c.KeyStoreDir == "" {
- keydir = filepath.Join(c.DataDir, datadirDefaultKeyStore)
- } else {
- keydir, err = filepath.Abs(c.KeyStoreDir)
- }
- case c.KeyStoreDir != "":
- keydir, err = filepath.Abs(c.KeyStoreDir)
- }
- return keydir, err
-}
-
-// GetKeyStoreDir retrieves the key directory and will create
-// and ephemeral one if necessary.
-func (c *Config) GetKeyStoreDir() (string, bool, error) {
- keydir, err := c.KeyDirConfig()
- if err != nil {
- return "", false, err
- }
- isEphemeral := false
- if keydir == "" {
- // There is no datadir.
- keydir, err = os.MkdirTemp("", "go-ethereum-keystore")
- isEphemeral = true
- }
-
- if err != nil {
- return "", false, err
- }
- if err := os.MkdirAll(keydir, 0700); err != nil {
- return "", false, err
- }
-
- return keydir, isEphemeral, nil
-}
diff --git a/node/config_test.go b/node/config_test.go
deleted file mode 100644
index e8af8ddcd8..0000000000
--- a/node/config_test.go
+++ /dev/null
@@ -1,153 +0,0 @@
-// 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 .
-
-package node
-
-import (
- "bytes"
- "os"
- "path/filepath"
- "runtime"
- "testing"
-
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/p2p"
-)
-
-// Tests that datadirs can be successfully created, be them manually configured
-// ones or automatically generated temporary ones.
-func TestDatadirCreation(t *testing.T) {
- // Create a temporary data dir and check that it can be used by a node
- dir := t.TempDir()
-
- node, err := New(&Config{DataDir: dir})
- if err != nil {
- t.Fatalf("failed to create stack with existing datadir: %v", err)
- }
- if err := node.Close(); err != nil {
- t.Fatalf("failed to close node: %v", err)
- }
- // Generate a long non-existing datadir path and check that it gets created by a node
- dir = filepath.Join(dir, "a", "b", "c", "d", "e", "f")
- node, err = New(&Config{DataDir: dir})
- if err != nil {
- t.Fatalf("failed to create stack with creatable datadir: %v", err)
- }
- if err := node.Close(); err != nil {
- t.Fatalf("failed to close node: %v", err)
- }
- if _, err := os.Stat(dir); err != nil {
- t.Fatalf("freshly created datadir not accessible: %v", err)
- }
- // Verify that an impossible datadir fails creation
- file, err := os.CreateTemp("", "")
- if err != nil {
- t.Fatalf("failed to create temporary file: %v", err)
- }
- defer func() {
- file.Close()
- os.Remove(file.Name())
- }()
-
- dir = filepath.Join(file.Name(), "invalid/path")
- _, err = New(&Config{DataDir: dir})
- if err == nil {
- t.Fatalf("protocol stack created with an invalid datadir")
- }
-}
-
-// Tests that IPC paths are correctly resolved to valid endpoints of different
-// platforms.
-func TestIPCPathResolution(t *testing.T) {
- var tests = []struct {
- DataDir string
- IPCPath string
- Windows bool
- Endpoint string
- }{
- {"", "", false, ""},
- {"data", "", false, ""},
- {"", "geth.ipc", false, filepath.Join(os.TempDir(), "geth.ipc")},
- {"data", "geth.ipc", false, "data/geth.ipc"},
- {"data", "./geth.ipc", false, "./geth.ipc"},
- {"data", "/geth.ipc", false, "/geth.ipc"},
- {"", "", true, ``},
- {"data", "", true, ``},
- {"", "geth.ipc", true, `\\.\pipe\geth.ipc`},
- {"data", "geth.ipc", true, `\\.\pipe\geth.ipc`},
- {"data", `\\.\pipe\geth.ipc`, true, `\\.\pipe\geth.ipc`},
- }
- for i, test := range tests {
- // Only run when platform/test match
- if (runtime.GOOS == "windows") == test.Windows {
- if endpoint := (&Config{DataDir: test.DataDir, IPCPath: test.IPCPath}).IPCEndpoint(); endpoint != test.Endpoint {
- t.Errorf("test %d: IPC endpoint mismatch: have %s, want %s", i, endpoint, test.Endpoint)
- }
- }
- }
-}
-
-// Tests that node keys can be correctly created, persisted, loaded and/or made
-// ephemeral.
-func TestNodeKeyPersistency(t *testing.T) {
- // Create a temporary folder and make sure no key is present
- dir := t.TempDir()
-
- keyfile := filepath.Join(dir, "unit-test", datadirPrivateKey)
-
- // Configure a node with a preset key and ensure it's not persisted
- key, err := crypto.GenerateKey()
- if err != nil {
- t.Fatalf("failed to generate one-shot node key: %v", err)
- }
- config := &Config{Name: "unit-test", DataDir: dir, P2P: p2p.Config{PrivateKey: key}}
- config.NodeKey()
- if _, err := os.Stat(keyfile); err == nil {
- t.Fatalf("one-shot node key persisted to data directory")
- }
-
- // Configure a node with no preset key and ensure it is persisted this time
- config = &Config{Name: "unit-test", DataDir: dir}
- config.NodeKey()
- if _, err := os.Stat(keyfile); err != nil {
- t.Fatalf("node key not persisted to data directory: %v", err)
- }
- if _, err = crypto.LoadECDSA(keyfile); err != nil {
- t.Fatalf("failed to load freshly persisted node key: %v", err)
- }
- blob1, err := os.ReadFile(keyfile)
- if err != nil {
- t.Fatalf("failed to read freshly persisted node key: %v", err)
- }
-
- // Configure a new node and ensure the previously persisted key is loaded
- config = &Config{Name: "unit-test", DataDir: dir}
- config.NodeKey()
- blob2, err := os.ReadFile(keyfile)
- if err != nil {
- t.Fatalf("failed to read previously persisted node key: %v", err)
- }
- if !bytes.Equal(blob1, blob2) {
- t.Fatalf("persisted node key mismatch: have %x, want %x", blob2, blob1)
- }
-
- // Configure ephemeral node and ensure no key is dumped locally
- config = &Config{Name: "unit-test", DataDir: ""}
- config.NodeKey()
- if _, err := os.Stat(filepath.Join(".", "unit-test", datadirPrivateKey)); err == nil {
- t.Fatalf("ephemeral node key persisted to disk")
- }
-}
diff --git a/node/defaults.go b/node/defaults.go
deleted file mode 100644
index 42d9d4cde0..0000000000
--- a/node/defaults.go
+++ /dev/null
@@ -1,133 +0,0 @@
-// Copyright 2016 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 .
-
-package node
-
-import (
- "os"
- "os/user"
- "path/filepath"
- "runtime"
-
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/nat"
- "github.com/ethereum/go-ethereum/rpc"
-)
-
-const (
- DefaultHTTPHost = "localhost" // Default host interface for the HTTP RPC server
- DefaultHTTPPort = 8545 // Default TCP port for the HTTP RPC server
- DefaultWSHost = "localhost" // Default host interface for the websocket RPC server
- DefaultWSPort = 8546 // Default TCP port for the websocket RPC server
- DefaultAuthHost = "localhost" // Default host interface for the authenticated apis
- DefaultAuthPort = 8551 // Default port for the authenticated apis
-)
-
-const (
- // Engine API batch limits: these are not configurable by users, and should cover the
- // needs of all CLs.
- engineAPIBatchItemLimit = 2000
- engineAPIBatchResponseSizeLimit = 250 * 1000 * 1000
-)
-
-var (
- DefaultAuthCors = []string{"localhost"} // Default cors domain for the authenticated apis
- DefaultAuthVhosts = []string{"localhost"} // Default virtual hosts for the authenticated apis
- DefaultAuthOrigins = []string{"localhost"} // Default origins for the authenticated apis
- DefaultAuthPrefix = "" // Default prefix for the authenticated apis
- DefaultAuthModules = []string{"eth", "engine"}
-)
-
-// DefaultConfig contains reasonable default settings.
-var DefaultConfig = Config{
- DataDir: DefaultDataDir(),
- HTTPPort: DefaultHTTPPort,
- AuthAddr: DefaultAuthHost,
- AuthPort: DefaultAuthPort,
- AuthVirtualHosts: DefaultAuthVhosts,
- HTTPModules: []string{"net", "web3"},
- HTTPVirtualHosts: []string{"localhost"},
- HTTPTimeouts: rpc.DefaultHTTPTimeouts,
- WSPort: DefaultWSPort,
- WSModules: []string{"net", "web3"},
- BatchRequestLimit: 1000,
- BatchResponseMaxSize: 25 * 1000 * 1000,
- GraphQLVirtualHosts: []string{"localhost"},
- P2P: p2p.Config{
- ListenAddr: ":30303",
- MaxPeers: 50,
- NAT: nat.Any(),
- },
- DBEngine: "", // Use whatever exists, will default to Pebble if non-existent and supported
-}
-
-// DefaultDataDir is the default data directory to use for the databases and other
-// persistence requirements.
-func DefaultDataDir() string {
- // Try to place the data folder in the user's home dir
- home := homeDir()
- if home != "" {
- switch runtime.GOOS {
- case "darwin":
- return filepath.Join(home, "Library", "Ethereum")
- case "windows":
- // We used to put everything in %HOME%\AppData\Roaming, but this caused
- // problems with non-typical setups. If this fallback location exists and
- // is non-empty, use it, otherwise DTRT and check %LOCALAPPDATA%.
- fallback := filepath.Join(home, "AppData", "Roaming", "Ethereum")
- appdata := windowsAppData()
- if appdata == "" || isNonEmptyDir(fallback) {
- return fallback
- }
- return filepath.Join(appdata, "Ethereum")
- default:
- return filepath.Join(home, ".ethereum")
- }
- }
- // As we cannot guess a stable location, return empty and handle later
- return ""
-}
-
-func windowsAppData() string {
- v := os.Getenv("LOCALAPPDATA")
- if v == "" {
- // Windows XP and below don't have LocalAppData. Crash here because
- // we don't support Windows XP and undefining the variable will cause
- // other issues.
- panic("environment variable LocalAppData is undefined")
- }
- return v
-}
-
-func isNonEmptyDir(dir string) bool {
- f, err := os.Open(dir)
- if err != nil {
- return false
- }
- names, _ := f.Readdir(1)
- f.Close()
- return len(names) > 0
-}
-
-func homeDir() string {
- if home := os.Getenv("HOME"); home != "" {
- return home
- }
- if usr, err := user.Current(); err == nil {
- return usr.HomeDir
- }
- return ""
-}
diff --git a/node/doc.go b/node/doc.go
deleted file mode 100644
index 4474e43660..0000000000
--- a/node/doc.go
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright 2016 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 .
-
-/*
-Package node sets up multi-protocol Ethereum nodes.
-
-In the model exposed by this package, a node is a collection of services which use shared
-resources to provide RPC APIs. Services can also offer devp2p protocols, which are wired
-up to the devp2p network when the node instance is started.
-
-# Node Lifecycle
-
-The Node object has a lifecycle consisting of three basic states, INITIALIZING, RUNNING
-and CLOSED.
-
- ●───────┐
- New()
- │
- ▼
- INITIALIZING ────Start()─┐
- │ │
- │ ▼
- Close() RUNNING
- │ │
- ▼ │
- CLOSED ◀──────Close()─┘
-
-Creating a Node allocates basic resources such as the data directory and returns the node
-in its INITIALIZING state. Lifecycle objects, RPC APIs and peer-to-peer networking
-protocols can be registered in this state. Basic operations such as opening a key-value
-database are permitted while initializing.
-
-Once everything is registered, the node can be started, which moves it into the RUNNING
-state. Starting the node starts all registered Lifecycle objects and enables RPC and
-peer-to-peer networking. Note that no additional Lifecycles, APIs or p2p protocols can be
-registered while the node is running.
-
-Closing the node releases all held resources. The actions performed by Close depend on the
-state it was in. When closing a node in INITIALIZING state, resources related to the data
-directory are released. If the node was RUNNING, closing it also stops all Lifecycle
-objects and shuts down RPC and peer-to-peer networking.
-
-You must always call Close on Node, even if the node was not started.
-
-# Resources Managed By Node
-
-All file-system resources used by a node instance are located in a directory called the
-data directory. The location of each resource can be overridden through additional node
-configuration. The data directory is optional. If it is not set and the location of a
-resource is otherwise unspecified, package node will create the resource in memory.
-
-To access to the devp2p network, Node configures and starts p2p.Server. Each host on the
-devp2p network has a unique identifier, the node key. The Node instance persists this key
-across restarts. Node also loads static and trusted node lists and ensures that knowledge
-about other hosts is persisted.
-
-JSON-RPC servers which run HTTP, WebSocket or IPC can be started on a Node. RPC modules
-offered by registered services will be offered on those endpoints. Users can restrict any
-endpoint to a subset of RPC modules. Node itself offers the "debug", "admin" and "web3"
-modules.
-
-Service implementations can open LevelDB databases through the service context. Package
-node chooses the file system location of each database. If the node is configured to run
-without a data directory, databases are opened in memory instead.
-
-Node also creates the shared store of encrypted Ethereum account keys. Services can access
-the account manager through the service context.
-
-# Sharing Data Directory Among Instances
-
-Multiple node instances can share a single data directory if they have distinct instance
-names (set through the Name config option). Sharing behaviour depends on the type of
-resource.
-
-devp2p-related resources (node key, static/trusted node lists, known hosts database) are
-stored in a directory with the same name as the instance. Thus, multiple node instances
-using the same data directory will store this information in different subdirectories of
-the data directory.
-
-LevelDB databases are also stored within the instance subdirectory. If multiple node
-instances use the same data directory, opening the databases with identical names will
-create one database for each instance.
-
-The account key store is shared among all node instances using the same data directory
-unless its location is changed through the KeyStoreDir configuration option.
-
-# Data Directory Sharing Example
-
-In this example, two node instances named A and B are started with the same data
-directory. Node instance A opens the database "db", node instance B opens the databases
-"db" and "db-2". The following files will be created in the data directory:
-
- data-directory/
- A/
- nodekey -- devp2p node key of instance A
- nodes/ -- devp2p discovery knowledge database of instance A
- db/ -- LevelDB content for "db"
- A.ipc -- JSON-RPC UNIX domain socket endpoint of instance A
- B/
- nodekey -- devp2p node key of node B
- nodes/ -- devp2p discovery knowledge database of instance B
- static-nodes.json -- devp2p static node list of instance B
- db/ -- LevelDB content for "db"
- db-2/ -- LevelDB content for "db-2"
- B.ipc -- JSON-RPC UNIX domain socket endpoint of instance B
- keystore/ -- account key store, used by both instances
-*/
-package node
diff --git a/node/endpoints.go b/node/endpoints.go
deleted file mode 100644
index 14c12fd1f1..0000000000
--- a/node/endpoints.go
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright 2020 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 .
-
-package node
-
-import (
- "net"
- "net/http"
- "time"
-
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/rpc"
-)
-
-// StartHTTPEndpoint starts the HTTP RPC endpoint.
-func StartHTTPEndpoint(endpoint string, timeouts rpc.HTTPTimeouts, handler http.Handler) (*http.Server, net.Addr, error) {
- // start the HTTP listener
- var (
- listener net.Listener
- err error
- )
- if listener, err = net.Listen("tcp", endpoint); err != nil {
- return nil, nil, err
- }
- // make sure timeout values are meaningful
- CheckTimeouts(&timeouts)
- // Bundle and start the HTTP server
- httpSrv := &http.Server{
- Handler: handler,
- ReadTimeout: timeouts.ReadTimeout,
- ReadHeaderTimeout: timeouts.ReadHeaderTimeout,
- WriteTimeout: timeouts.WriteTimeout,
- IdleTimeout: timeouts.IdleTimeout,
- }
- go httpSrv.Serve(listener)
- return httpSrv, listener.Addr(), err
-}
-
-// checkModuleAvailability checks that all names given in modules are actually
-// available API services. It assumes that the MetadataApi module ("rpc") is always available;
-// the registration of this "rpc" module happens in NewServer() and is thus common to all endpoints.
-func checkModuleAvailability(modules []string, apis []rpc.API) (bad, available []string) {
- availableSet := make(map[string]struct{})
- for _, api := range apis {
- if _, ok := availableSet[api.Namespace]; !ok {
- availableSet[api.Namespace] = struct{}{}
- available = append(available, api.Namespace)
- }
- }
- for _, name := range modules {
- if _, ok := availableSet[name]; !ok {
- if name != rpc.MetadataApi && name != rpc.EngineApi {
- bad = append(bad, name)
- }
- }
- }
- return bad, available
-}
-
-// CheckTimeouts ensures that timeout values are meaningful
-func CheckTimeouts(timeouts *rpc.HTTPTimeouts) {
- if timeouts.ReadTimeout < time.Second {
- log.Warn("Sanitizing invalid HTTP read timeout", "provided", timeouts.ReadTimeout, "updated", rpc.DefaultHTTPTimeouts.ReadTimeout)
- timeouts.ReadTimeout = rpc.DefaultHTTPTimeouts.ReadTimeout
- }
- if timeouts.ReadHeaderTimeout < time.Second {
- log.Warn("Sanitizing invalid HTTP read header timeout", "provided", timeouts.ReadHeaderTimeout, "updated", rpc.DefaultHTTPTimeouts.ReadHeaderTimeout)
- timeouts.ReadHeaderTimeout = rpc.DefaultHTTPTimeouts.ReadHeaderTimeout
- }
- if timeouts.WriteTimeout < time.Second {
- log.Warn("Sanitizing invalid HTTP write timeout", "provided", timeouts.WriteTimeout, "updated", rpc.DefaultHTTPTimeouts.WriteTimeout)
- timeouts.WriteTimeout = rpc.DefaultHTTPTimeouts.WriteTimeout
- }
- if timeouts.IdleTimeout < time.Second {
- log.Warn("Sanitizing invalid HTTP idle timeout", "provided", timeouts.IdleTimeout, "updated", rpc.DefaultHTTPTimeouts.IdleTimeout)
- timeouts.IdleTimeout = rpc.DefaultHTTPTimeouts.IdleTimeout
- }
-}
diff --git a/node/errors.go b/node/errors.go
deleted file mode 100644
index 67547bf691..0000000000
--- a/node/errors.go
+++ /dev/null
@@ -1,52 +0,0 @@
-// 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 .
-
-package node
-
-import (
- "errors"
- "fmt"
- "reflect"
- "syscall"
-)
-
-var (
- ErrDatadirUsed = errors.New("datadir already used by another process")
- ErrNodeStopped = errors.New("node not started")
- ErrNodeRunning = errors.New("node already running")
- ErrServiceUnknown = errors.New("unknown service")
-
- datadirInUseErrnos = map[uint]bool{11: true, 32: true, 35: true}
-)
-
-func convertFileLockError(err error) error {
- if errno, ok := err.(syscall.Errno); ok && datadirInUseErrnos[uint(errno)] {
- return ErrDatadirUsed
- }
- return err
-}
-
-// StopError is returned if a Node fails to stop either any of its registered
-// services or itself.
-type StopError struct {
- Server error
- Services map[reflect.Type]error
-}
-
-// Error generates a textual representation of the stop error.
-func (e *StopError) Error() string {
- return fmt.Sprintf("server: %v, services: %v", e.Server, e.Services)
-}
diff --git a/node/jwt_auth.go b/node/jwt_auth.go
deleted file mode 100644
index d4f8193ca7..0000000000
--- a/node/jwt_auth.go
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2022 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 .
-
-package node
-
-import (
- "fmt"
- "net/http"
- "time"
-
- "github.com/ethereum/go-ethereum/rpc"
- "github.com/golang-jwt/jwt/v4"
-)
-
-// NewJWTAuth creates an rpc client authentication provider that uses JWT. The
-// secret MUST be 32 bytes (256 bits) as defined by the Engine-API authentication spec.
-//
-// See https://github.com/ethereum/execution-apis/blob/main/src/engine/authentication.md
-// for more details about this authentication scheme.
-func NewJWTAuth(jwtsecret [32]byte) rpc.HTTPAuth {
- return func(h http.Header) error {
- token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
- "iat": &jwt.NumericDate{Time: time.Now()},
- })
- s, err := token.SignedString(jwtsecret[:])
- if err != nil {
- return fmt.Errorf("failed to create JWT token: %w", err)
- }
- h.Set("Authorization", "Bearer "+s)
- return nil
- }
-}
diff --git a/node/jwt_handler.go b/node/jwt_handler.go
deleted file mode 100644
index 637ae19686..0000000000
--- a/node/jwt_handler.go
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2022 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 .
-
-package node
-
-import (
- "net/http"
- "strings"
- "time"
-
- "github.com/golang-jwt/jwt/v4"
-)
-
-const jwtExpiryTimeout = 60 * time.Second
-
-type jwtHandler struct {
- keyFunc func(token *jwt.Token) (interface{}, error)
- next http.Handler
-}
-
-// newJWTHandler creates a http.Handler with jwt authentication support.
-func newJWTHandler(secret []byte, next http.Handler) http.Handler {
- return &jwtHandler{
- keyFunc: func(token *jwt.Token) (interface{}, error) {
- return secret, nil
- },
- next: next,
- }
-}
-
-// ServeHTTP implements http.Handler
-func (handler *jwtHandler) ServeHTTP(out http.ResponseWriter, r *http.Request) {
- var (
- strToken string
- claims jwt.RegisteredClaims
- )
- if auth := r.Header.Get("Authorization"); strings.HasPrefix(auth, "Bearer ") {
- strToken = strings.TrimPrefix(auth, "Bearer ")
- }
- if len(strToken) == 0 {
- http.Error(out, "missing token", http.StatusUnauthorized)
- return
- }
- // We explicitly set only HS256 allowed, and also disables the
- // claim-check: the RegisteredClaims internally requires 'iat' to
- // be no later than 'now', but we allow for a bit of drift.
- token, err := jwt.ParseWithClaims(strToken, &claims, handler.keyFunc,
- jwt.WithValidMethods([]string{"HS256"}),
- jwt.WithoutClaimsValidation())
-
- switch {
- case err != nil:
- http.Error(out, err.Error(), http.StatusUnauthorized)
- case !token.Valid:
- http.Error(out, "invalid token", http.StatusUnauthorized)
- case !claims.VerifyExpiresAt(time.Now(), false): // optional
- http.Error(out, "token is expired", http.StatusUnauthorized)
- case claims.IssuedAt == nil:
- http.Error(out, "missing issued-at", http.StatusUnauthorized)
- case time.Since(claims.IssuedAt.Time) > jwtExpiryTimeout:
- http.Error(out, "stale token", http.StatusUnauthorized)
- case time.Until(claims.IssuedAt.Time) > jwtExpiryTimeout:
- http.Error(out, "future token", http.StatusUnauthorized)
- default:
- handler.next.ServeHTTP(out, r)
- }
-}
diff --git a/node/lifecycle.go b/node/lifecycle.go
deleted file mode 100644
index 0d5f9a0680..0000000000
--- a/node/lifecycle.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2020 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 .
-
-package node
-
-// Lifecycle encompasses the behavior of services that can be started and stopped
-// on the node. Lifecycle management is delegated to the node, but it is the
-// responsibility of the service-specific package to configure and register the
-// service on the node using the `RegisterLifecycle` method.
-type Lifecycle interface {
- // Start is called after all services have been constructed and the networking
- // layer was also initialized to spawn any goroutines required by the service.
- Start() error
-
- // Stop terminates all goroutines belonging to the service, blocking until they
- // are all terminated.
- Stop() error
-}
diff --git a/node/node.go b/node/node.go
deleted file mode 100644
index 41c9971fe8..0000000000
--- a/node/node.go
+++ /dev/null
@@ -1,822 +0,0 @@
-// 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 .
-
-package node
-
-import (
- crand "crypto/rand"
- "errors"
- "fmt"
- "hash/crc32"
- "net/http"
- "os"
- "path/filepath"
- "reflect"
- "strings"
- "sync"
-
- "github.com/ethereum/go-ethereum/accounts"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/rpc"
- "github.com/gofrs/flock"
-)
-
-// Node is a container on which services can be registered.
-type Node struct {
- eventmux *event.TypeMux
- config *Config
- accman *accounts.Manager
- log log.Logger
- keyDir string // key store directory
- keyDirTemp bool // If true, key directory will be removed by Stop
- dirLock *flock.Flock // prevents concurrent use of instance directory
- stop chan struct{} // Channel to wait for termination notifications
- server *p2p.Server // Currently running P2P networking layer
- startStopLock sync.Mutex // Start/Stop are protected by an additional lock
- state int // Tracks state of node lifecycle
-
- lock sync.Mutex
- lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle
- rpcAPIs []rpc.API // List of APIs currently provided by the node
- http *httpServer //
- ws *httpServer //
- httpAuth *httpServer //
- wsAuth *httpServer //
- ipc *ipcServer // Stores information about the ipc http server
- inprocHandler *rpc.Server // In-process RPC request handler to process the API requests
-
- databases map[*closeTrackingDB]struct{} // All open databases
-}
-
-const (
- initializingState = iota
- runningState
- closedState
-)
-
-// New creates a new P2P node, ready for protocol registration.
-func New(conf *Config) (*Node, error) {
- // Copy config and resolve the datadir so future changes to the current
- // working directory don't affect the node.
- confCopy := *conf
- conf = &confCopy
- if conf.DataDir != "" {
- absdatadir, err := filepath.Abs(conf.DataDir)
- if err != nil {
- return nil, err
- }
- conf.DataDir = absdatadir
- }
- if conf.Logger == nil {
- conf.Logger = log.New()
- }
-
- // Ensure that the instance name doesn't cause weird conflicts with
- // other files in the data directory.
- if strings.ContainsAny(conf.Name, `/\`) {
- return nil, errors.New(`Config.Name must not contain '/' or '\'`)
- }
- if conf.Name == datadirDefaultKeyStore {
- return nil, errors.New(`Config.Name cannot be "` + datadirDefaultKeyStore + `"`)
- }
- if strings.HasSuffix(conf.Name, ".ipc") {
- return nil, errors.New(`Config.Name cannot end in ".ipc"`)
- }
- server := rpc.NewServer()
- server.SetBatchLimits(conf.BatchRequestLimit, conf.BatchResponseMaxSize)
- node := &Node{
- config: conf,
- inprocHandler: server,
- eventmux: new(event.TypeMux),
- log: conf.Logger,
- stop: make(chan struct{}),
- server: &p2p.Server{Config: conf.P2P},
- databases: make(map[*closeTrackingDB]struct{}),
- }
-
- // Register built-in APIs.
- node.rpcAPIs = append(node.rpcAPIs, node.apis()...)
-
- // Acquire the instance directory lock.
- if err := node.openDataDir(); err != nil {
- return nil, err
- }
- keyDir, isEphem, err := conf.GetKeyStoreDir()
- if err != nil {
- return nil, err
- }
- node.keyDir = keyDir
- node.keyDirTemp = isEphem
- // Creates an empty AccountManager with no backends. Callers (e.g. cmd/geth)
- // are required to add the backends later on.
- node.accman = accounts.NewManager(&accounts.Config{InsecureUnlockAllowed: conf.InsecureUnlockAllowed})
-
- // Initialize the p2p server. This creates the node key and discovery databases.
- node.server.Config.PrivateKey = node.config.NodeKey()
- node.server.Config.Name = node.config.NodeName()
- node.server.Config.Logger = node.log
- node.config.checkLegacyFiles()
- if node.server.Config.NodeDatabase == "" {
- node.server.Config.NodeDatabase = node.config.NodeDB()
- }
-
- // Check HTTP/WS prefixes are valid.
- if err := validatePrefix("HTTP", conf.HTTPPathPrefix); err != nil {
- return nil, err
- }
- if err := validatePrefix("WebSocket", conf.WSPathPrefix); err != nil {
- return nil, err
- }
-
- // Configure RPC servers.
- node.http = newHTTPServer(node.log, conf.HTTPTimeouts)
- node.httpAuth = newHTTPServer(node.log, conf.HTTPTimeouts)
- node.ws = newHTTPServer(node.log, rpc.DefaultHTTPTimeouts)
- node.wsAuth = newHTTPServer(node.log, rpc.DefaultHTTPTimeouts)
- node.ipc = newIPCServer(node.log, conf.IPCEndpoint())
-
- return node, nil
-}
-
-// Start starts all registered lifecycles, RPC services and p2p networking.
-// Node can only be started once.
-func (n *Node) Start() error {
- n.startStopLock.Lock()
- defer n.startStopLock.Unlock()
-
- n.lock.Lock()
- switch n.state {
- case runningState:
- n.lock.Unlock()
- return ErrNodeRunning
- case closedState:
- n.lock.Unlock()
- return ErrNodeStopped
- }
- n.state = runningState
- // open networking and RPC endpoints
- err := n.openEndpoints()
- lifecycles := make([]Lifecycle, len(n.lifecycles))
- copy(lifecycles, n.lifecycles)
- n.lock.Unlock()
-
- // Check if endpoint startup failed.
- if err != nil {
- n.doClose(nil)
- return err
- }
- // Start all registered lifecycles.
- var started []Lifecycle
- for _, lifecycle := range lifecycles {
- if err = lifecycle.Start(); err != nil {
- break
- }
- started = append(started, lifecycle)
- }
- // Check if any lifecycle failed to start.
- if err != nil {
- n.stopServices(started)
- n.doClose(nil)
- }
- return err
-}
-
-// Close stops the Node and releases resources acquired in
-// Node constructor New.
-func (n *Node) Close() error {
- n.startStopLock.Lock()
- defer n.startStopLock.Unlock()
-
- n.lock.Lock()
- state := n.state
- n.lock.Unlock()
- switch state {
- case initializingState:
- // The node was never started.
- return n.doClose(nil)
- case runningState:
- // The node was started, release resources acquired by Start().
- var errs []error
- if err := n.stopServices(n.lifecycles); err != nil {
- errs = append(errs, err)
- }
- return n.doClose(errs)
- case closedState:
- return ErrNodeStopped
- default:
- panic(fmt.Sprintf("node is in unknown state %d", state))
- }
-}
-
-// doClose releases resources acquired by New(), collecting errors.
-func (n *Node) doClose(errs []error) error {
- // Close databases. This needs the lock because it needs to
- // synchronize with OpenDatabase*.
- n.lock.Lock()
- n.state = closedState
- errs = append(errs, n.closeDatabases()...)
- n.lock.Unlock()
-
- if err := n.accman.Close(); err != nil {
- errs = append(errs, err)
- }
- if n.keyDirTemp {
- if err := os.RemoveAll(n.keyDir); err != nil {
- errs = append(errs, err)
- }
- }
-
- // Release instance directory lock.
- n.closeDataDir()
-
- // Unblock n.Wait.
- close(n.stop)
-
- // Report any errors that might have occurred.
- switch len(errs) {
- case 0:
- return nil
- case 1:
- return errs[0]
- default:
- return fmt.Errorf("%v", errs)
- }
-}
-
-// openEndpoints starts all network and RPC endpoints.
-func (n *Node) openEndpoints() error {
- // start networking endpoints
- n.log.Info("Starting peer-to-peer node", "instance", n.server.Name)
- if err := n.server.Start(); err != nil {
- return convertFileLockError(err)
- }
- // start RPC endpoints
- err := n.startRPC()
- if err != nil {
- n.stopRPC()
- n.server.Stop()
- }
- return err
-}
-
-// containsLifecycle checks if 'lfs' contains 'l'.
-func containsLifecycle(lfs []Lifecycle, l Lifecycle) bool {
- for _, obj := range lfs {
- if obj == l {
- return true
- }
- }
- return false
-}
-
-// stopServices terminates running services, RPC and p2p networking.
-// It is the inverse of Start.
-func (n *Node) stopServices(running []Lifecycle) error {
- n.stopRPC()
-
- // Stop running lifecycles in reverse order.
- failure := &StopError{Services: make(map[reflect.Type]error)}
- for i := len(running) - 1; i >= 0; i-- {
- if err := running[i].Stop(); err != nil {
- failure.Services[reflect.TypeOf(running[i])] = err
- }
- }
-
- // Stop p2p networking.
- n.server.Stop()
-
- if len(failure.Services) > 0 {
- return failure
- }
- return nil
-}
-
-func (n *Node) openDataDir() error {
- if n.config.DataDir == "" {
- return nil // ephemeral
- }
-
- instdir := filepath.Join(n.config.DataDir, n.config.name())
- if err := os.MkdirAll(instdir, 0700); err != nil {
- return err
- }
- // Lock the instance directory to prevent concurrent use by another instance as well as
- // accidental use of the instance directory as a database.
- n.dirLock = flock.New(filepath.Join(instdir, "LOCK"))
-
- if locked, err := n.dirLock.TryLock(); err != nil {
- return err
- } else if !locked {
- return ErrDatadirUsed
- }
- return nil
-}
-
-func (n *Node) closeDataDir() {
- // Release instance directory lock.
- if n.dirLock != nil && n.dirLock.Locked() {
- n.dirLock.Unlock()
- n.dirLock = nil
- }
-}
-
-// obtainJWTSecret loads the jwt-secret, either from the provided config,
-// or from the default location. If neither of those are present, it generates
-// a new secret and stores to the default location.
-func (n *Node) obtainJWTSecret(cliParam string) ([]byte, error) {
- fileName := cliParam
- if len(fileName) == 0 {
- // no path provided, use default
- fileName = n.ResolvePath(datadirJWTKey)
- }
- // try reading from file
- if data, err := os.ReadFile(fileName); err == nil {
- jwtSecret := common.FromHex(strings.TrimSpace(string(data)))
- if len(jwtSecret) == 32 {
- log.Info("Loaded JWT secret file", "path", fileName, "crc32", fmt.Sprintf("%#x", crc32.ChecksumIEEE(jwtSecret)))
- return jwtSecret, nil
- }
- log.Error("Invalid JWT secret", "path", fileName, "length", len(jwtSecret))
- return nil, errors.New("invalid JWT secret")
- }
- // Need to generate one
- jwtSecret := make([]byte, 32)
- crand.Read(jwtSecret)
- // if we're in --dev mode, don't bother saving, just show it
- if fileName == "" {
- log.Info("Generated ephemeral JWT secret", "secret", hexutil.Encode(jwtSecret))
- return jwtSecret, nil
- }
- if err := os.WriteFile(fileName, []byte(hexutil.Encode(jwtSecret)), 0600); err != nil {
- return nil, err
- }
- log.Info("Generated JWT secret", "path", fileName)
- return jwtSecret, nil
-}
-
-// startRPC is a helper method to configure all the various RPC endpoints during node
-// startup. It's not meant to be called at any time afterwards as it makes certain
-// assumptions about the state of the node.
-func (n *Node) startRPC() error {
- // Filter out personal api
- var apis []rpc.API
- for _, api := range n.rpcAPIs {
- if api.Namespace == "personal" {
- if n.config.EnablePersonal {
- log.Warn("Deprecated personal namespace activated")
- } else {
- continue
- }
- }
- apis = append(apis, api)
- }
- if err := n.startInProc(apis); err != nil {
- return err
- }
-
- // Configure IPC.
- if n.ipc.endpoint != "" {
- if err := n.ipc.start(apis); err != nil {
- return err
- }
- }
- var (
- servers []*httpServer
- openAPIs, allAPIs = n.getAPIs()
- )
-
- rpcConfig := rpcEndpointConfig{
- batchItemLimit: n.config.BatchRequestLimit,
- batchResponseSizeLimit: n.config.BatchResponseMaxSize,
- }
-
- initHttp := func(server *httpServer, port int) error {
- if err := server.setListenAddr(n.config.HTTPHost, port); err != nil {
- return err
- }
- if err := server.enableRPC(openAPIs, httpConfig{
- CorsAllowedOrigins: n.config.HTTPCors,
- Vhosts: n.config.HTTPVirtualHosts,
- Modules: n.config.HTTPModules,
- prefix: n.config.HTTPPathPrefix,
- rpcEndpointConfig: rpcConfig,
- }); err != nil {
- return err
- }
- servers = append(servers, server)
- return nil
- }
-
- initWS := func(port int) error {
- server := n.wsServerForPort(port, false)
- if err := server.setListenAddr(n.config.WSHost, port); err != nil {
- return err
- }
- if err := server.enableWS(openAPIs, wsConfig{
- Modules: n.config.WSModules,
- Origins: n.config.WSOrigins,
- prefix: n.config.WSPathPrefix,
- rpcEndpointConfig: rpcConfig,
- }); err != nil {
- return err
- }
- servers = append(servers, server)
- return nil
- }
-
- initAuth := func(port int, secret []byte) error {
- // Enable auth via HTTP
- server := n.httpAuth
- if err := server.setListenAddr(n.config.AuthAddr, port); err != nil {
- return err
- }
- sharedConfig := rpcEndpointConfig{
- jwtSecret: secret,
- batchItemLimit: engineAPIBatchItemLimit,
- batchResponseSizeLimit: engineAPIBatchResponseSizeLimit,
- }
- if err := server.enableRPC(allAPIs, httpConfig{
- CorsAllowedOrigins: DefaultAuthCors,
- Vhosts: n.config.AuthVirtualHosts,
- Modules: DefaultAuthModules,
- prefix: DefaultAuthPrefix,
- rpcEndpointConfig: sharedConfig,
- }); err != nil {
- return err
- }
- servers = append(servers, server)
-
- // Enable auth via WS
- server = n.wsServerForPort(port, true)
- if err := server.setListenAddr(n.config.AuthAddr, port); err != nil {
- return err
- }
- if err := server.enableWS(allAPIs, wsConfig{
- Modules: DefaultAuthModules,
- Origins: DefaultAuthOrigins,
- prefix: DefaultAuthPrefix,
- rpcEndpointConfig: sharedConfig,
- }); err != nil {
- return err
- }
- servers = append(servers, server)
- return nil
- }
-
- // Set up HTTP.
- if n.config.HTTPHost != "" {
- // Configure legacy unauthenticated HTTP.
- if err := initHttp(n.http, n.config.HTTPPort); err != nil {
- return err
- }
- }
- // Configure WebSocket.
- if n.config.WSHost != "" {
- // legacy unauthenticated
- if err := initWS(n.config.WSPort); err != nil {
- return err
- }
- }
- // Configure authenticated API
- if len(openAPIs) != len(allAPIs) {
- jwtSecret, err := n.obtainJWTSecret(n.config.JWTSecret)
- if err != nil {
- return err
- }
- if err := initAuth(n.config.AuthPort, jwtSecret); err != nil {
- return err
- }
- }
- // Start the servers
- for _, server := range servers {
- if err := server.start(); err != nil {
- return err
- }
- }
- return nil
-}
-
-func (n *Node) wsServerForPort(port int, authenticated bool) *httpServer {
- httpServer, wsServer := n.http, n.ws
- if authenticated {
- httpServer, wsServer = n.httpAuth, n.wsAuth
- }
- if n.config.HTTPHost == "" || httpServer.port == port {
- return httpServer
- }
- return wsServer
-}
-
-func (n *Node) stopRPC() {
- n.http.stop()
- n.ws.stop()
- n.httpAuth.stop()
- n.wsAuth.stop()
- n.ipc.stop()
- n.stopInProc()
-}
-
-// startInProc registers all RPC APIs on the inproc server.
-func (n *Node) startInProc(apis []rpc.API) error {
- for _, api := range apis {
- if err := n.inprocHandler.RegisterName(api.Namespace, api.Service); err != nil {
- return err
- }
- }
- return nil
-}
-
-// stopInProc terminates the in-process RPC endpoint.
-func (n *Node) stopInProc() {
- n.inprocHandler.Stop()
-}
-
-// Wait blocks until the node is closed.
-func (n *Node) Wait() {
- <-n.stop
-}
-
-// RegisterLifecycle registers the given Lifecycle on the node.
-func (n *Node) RegisterLifecycle(lifecycle Lifecycle) {
- n.lock.Lock()
- defer n.lock.Unlock()
-
- if n.state != initializingState {
- panic("can't register lifecycle on running/stopped node")
- }
- if containsLifecycle(n.lifecycles, lifecycle) {
- panic(fmt.Sprintf("attempt to register lifecycle %T more than once", lifecycle))
- }
- n.lifecycles = append(n.lifecycles, lifecycle)
-}
-
-// RegisterProtocols adds backend's protocols to the node's p2p server.
-func (n *Node) RegisterProtocols(protocols []p2p.Protocol) {
- n.lock.Lock()
- defer n.lock.Unlock()
-
- if n.state != initializingState {
- panic("can't register protocols on running/stopped node")
- }
- n.server.Protocols = append(n.server.Protocols, protocols...)
-}
-
-// RegisterAPIs registers the APIs a service provides on the node.
-func (n *Node) RegisterAPIs(apis []rpc.API) {
- n.lock.Lock()
- defer n.lock.Unlock()
-
- if n.state != initializingState {
- panic("can't register APIs on running/stopped node")
- }
- n.rpcAPIs = append(n.rpcAPIs, apis...)
-}
-
-// getAPIs return two sets of APIs, both the ones that do not require
-// authentication, and the complete set
-func (n *Node) getAPIs() (unauthenticated, all []rpc.API) {
- for _, api := range n.rpcAPIs {
- if !api.Authenticated {
- unauthenticated = append(unauthenticated, api)
- }
- }
- return unauthenticated, n.rpcAPIs
-}
-
-// RegisterHandler mounts a handler on the given path on the canonical HTTP server.
-//
-// The name of the handler is shown in a log message when the HTTP server starts
-// and should be a descriptive term for the service provided by the handler.
-func (n *Node) RegisterHandler(name, path string, handler http.Handler) {
- n.lock.Lock()
- defer n.lock.Unlock()
-
- if n.state != initializingState {
- panic("can't register HTTP handler on running/stopped node")
- }
-
- n.http.mux.Handle(path, handler)
- n.http.handlerNames[path] = name
-}
-
-// Attach creates an RPC client attached to an in-process API handler.
-func (n *Node) Attach() *rpc.Client {
- return rpc.DialInProc(n.inprocHandler)
-}
-
-// RPCHandler returns the in-process RPC request handler.
-func (n *Node) RPCHandler() (*rpc.Server, error) {
- n.lock.Lock()
- defer n.lock.Unlock()
-
- if n.state == closedState {
- return nil, ErrNodeStopped
- }
- return n.inprocHandler, nil
-}
-
-// Config returns the configuration of node.
-func (n *Node) Config() *Config {
- return n.config
-}
-
-// Server retrieves the currently running P2P network layer. This method is meant
-// only to inspect fields of the currently running server. Callers should not
-// start or stop the returned server.
-func (n *Node) Server() *p2p.Server {
- n.lock.Lock()
- defer n.lock.Unlock()
-
- return n.server
-}
-
-// DataDir retrieves the current datadir used by the protocol stack.
-// Deprecated: No files should be stored in this directory, use InstanceDir instead.
-func (n *Node) DataDir() string {
- return n.config.DataDir
-}
-
-// InstanceDir retrieves the instance directory used by the protocol stack.
-func (n *Node) InstanceDir() string {
- return n.config.instanceDir()
-}
-
-// KeyStoreDir retrieves the key directory
-func (n *Node) KeyStoreDir() string {
- return n.keyDir
-}
-
-// AccountManager retrieves the account manager used by the protocol stack.
-func (n *Node) AccountManager() *accounts.Manager {
- return n.accman
-}
-
-// IPCEndpoint retrieves the current IPC endpoint used by the protocol stack.
-func (n *Node) IPCEndpoint() string {
- return n.ipc.endpoint
-}
-
-// HTTPEndpoint returns the URL of the HTTP server. Note that this URL does not
-// contain the JSON-RPC path prefix set by HTTPPathPrefix.
-func (n *Node) HTTPEndpoint() string {
- return "http://" + n.http.listenAddr()
-}
-
-// WSEndpoint returns the current JSON-RPC over WebSocket endpoint.
-func (n *Node) WSEndpoint() string {
- if n.http.wsAllowed() {
- return "ws://" + n.http.listenAddr() + n.http.wsConfig.prefix
- }
- return "ws://" + n.ws.listenAddr() + n.ws.wsConfig.prefix
-}
-
-// HTTPAuthEndpoint returns the URL of the authenticated HTTP server.
-func (n *Node) HTTPAuthEndpoint() string {
- return "http://" + n.httpAuth.listenAddr()
-}
-
-// WSAuthEndpoint returns the current authenticated JSON-RPC over WebSocket endpoint.
-func (n *Node) WSAuthEndpoint() string {
- if n.httpAuth.wsAllowed() {
- return "ws://" + n.httpAuth.listenAddr() + n.httpAuth.wsConfig.prefix
- }
- return "ws://" + n.wsAuth.listenAddr() + n.wsAuth.wsConfig.prefix
-}
-
-// EventMux retrieves the event multiplexer used by all the network services in
-// the current protocol stack.
-func (n *Node) EventMux() *event.TypeMux {
- return n.eventmux
-}
-
-// OpenDatabase opens an existing database with the given name (or creates one if no
-// previous can be found) from within the node's instance directory. If the node is
-// ephemeral, a memory database is returned.
-func (n *Node) OpenDatabase(name string, cache, handles int, namespace string, readonly bool) (ethdb.Database, error) {
- n.lock.Lock()
- defer n.lock.Unlock()
- if n.state == closedState {
- return nil, ErrNodeStopped
- }
-
- var db ethdb.Database
- var err error
- if n.config.DataDir == "" {
- db = rawdb.NewMemoryDatabase()
- } else {
- db, err = rawdb.Open(rawdb.OpenOptions{
- Type: n.config.DBEngine,
- Directory: n.ResolvePath(name),
- Namespace: namespace,
- Cache: cache,
- Handles: handles,
- ReadOnly: readonly,
- })
- }
-
- if err == nil {
- db = n.wrapDatabase(db)
- }
- return db, err
-}
-
-// OpenDatabaseWithFreezer opens an existing database with the given name (or
-// creates one if no previous can be found) from within the node's data directory,
-// also attaching a chain freezer to it that moves ancient chain data from the
-// database to immutable append-only files. If the node is an ephemeral one, a
-// memory database is returned.
-func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, ancient string, namespace string, readonly bool) (ethdb.Database, error) {
- n.lock.Lock()
- defer n.lock.Unlock()
- if n.state == closedState {
- return nil, ErrNodeStopped
- }
- var db ethdb.Database
- var err error
- if n.config.DataDir == "" {
- db = rawdb.NewMemoryDatabase()
- } else {
- db, err = rawdb.Open(rawdb.OpenOptions{
- Type: n.config.DBEngine,
- Directory: n.ResolvePath(name),
- AncientsDirectory: n.ResolveAncient(name, ancient),
- Namespace: namespace,
- Cache: cache,
- Handles: handles,
- ReadOnly: readonly,
- })
- }
-
- if err == nil {
- db = n.wrapDatabase(db)
- }
- return db, err
-}
-
-// ResolvePath returns the absolute path of a resource in the instance directory.
-func (n *Node) ResolvePath(x string) string {
- return n.config.ResolvePath(x)
-}
-
-// ResolveAncient returns the absolute path of the root ancient directory.
-func (n *Node) ResolveAncient(name string, ancient string) string {
- switch {
- case ancient == "":
- ancient = filepath.Join(n.ResolvePath(name), "ancient")
- case !filepath.IsAbs(ancient):
- ancient = n.ResolvePath(ancient)
- }
- return ancient
-}
-
-// closeTrackingDB wraps the Close method of a database. When the database is closed by the
-// service, the wrapper removes it from the node's database map. This ensures that Node
-// won't auto-close the database if it is closed by the service that opened it.
-type closeTrackingDB struct {
- ethdb.Database
- n *Node
-}
-
-func (db *closeTrackingDB) Close() error {
- db.n.lock.Lock()
- delete(db.n.databases, db)
- db.n.lock.Unlock()
- return db.Database.Close()
-}
-
-// wrapDatabase ensures the database will be auto-closed when Node is closed.
-func (n *Node) wrapDatabase(db ethdb.Database) ethdb.Database {
- wrapper := &closeTrackingDB{db, n}
- n.databases[wrapper] = struct{}{}
- return wrapper
-}
-
-// closeDatabases closes all open databases.
-func (n *Node) closeDatabases() (errors []error) {
- for db := range n.databases {
- delete(n.databases, db)
- if err := db.Database.Close(); err != nil {
- errors = append(errors, err)
- }
- }
- return errors
-}
diff --git a/node/node_auth_test.go b/node/node_auth_test.go
deleted file mode 100644
index 597cd8531f..0000000000
--- a/node/node_auth_test.go
+++ /dev/null
@@ -1,237 +0,0 @@
-// Copyright 2022 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 .
-
-package node
-
-import (
- "context"
- crand "crypto/rand"
- "fmt"
- "net/http"
- "os"
- "path"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/ethereum/go-ethereum/rpc"
- "github.com/golang-jwt/jwt/v4"
-)
-
-type helloRPC string
-
-func (ta helloRPC) HelloWorld() (string, error) {
- return string(ta), nil
-}
-
-type authTest struct {
- name string
- endpoint string
- prov rpc.HTTPAuth
- expectDialFail bool
- expectCall1Fail bool
- expectCall2Fail bool
-}
-
-func (at *authTest) Run(t *testing.T) {
- ctx := context.Background()
- cl, err := rpc.DialOptions(ctx, at.endpoint, rpc.WithHTTPAuth(at.prov))
- if at.expectDialFail {
- if err == nil {
- t.Fatal("expected initial dial to fail")
- } else {
- return
- }
- }
- if err != nil {
- t.Fatalf("failed to dial rpc endpoint: %v", err)
- }
-
- var x string
- err = cl.CallContext(ctx, &x, "engine_helloWorld")
- if at.expectCall1Fail {
- if err == nil {
- t.Fatal("expected call 1 to fail")
- } else {
- return
- }
- }
- if err != nil {
- t.Fatalf("failed to call rpc endpoint: %v", err)
- }
- if x != "hello engine" {
- t.Fatalf("method was silent but did not return expected value: %q", x)
- }
-
- err = cl.CallContext(ctx, &x, "eth_helloWorld")
- if at.expectCall2Fail {
- if err == nil {
- t.Fatal("expected call 2 to fail")
- } else {
- return
- }
- }
- if err != nil {
- t.Fatalf("failed to call rpc endpoint: %v", err)
- }
- if x != "hello eth" {
- t.Fatalf("method was silent but did not return expected value: %q", x)
- }
-}
-
-func TestAuthEndpoints(t *testing.T) {
- var secret [32]byte
- if _, err := crand.Read(secret[:]); err != nil {
- t.Fatalf("failed to create jwt secret: %v", err)
- }
- // Geth must read it from a file, and does not support in-memory JWT secrets, so we create a temporary file.
- jwtPath := path.Join(t.TempDir(), "jwt_secret")
- if err := os.WriteFile(jwtPath, []byte(hexutil.Encode(secret[:])), 0600); err != nil {
- t.Fatalf("failed to prepare jwt secret file: %v", err)
- }
- // We get ports assigned by the node automatically
- conf := &Config{
- HTTPHost: "127.0.0.1",
- HTTPPort: 0,
- WSHost: "127.0.0.1",
- WSPort: 0,
- AuthAddr: "127.0.0.1",
- AuthPort: 0,
- JWTSecret: jwtPath,
-
- WSModules: []string{"eth", "engine"},
- HTTPModules: []string{"eth", "engine"},
- }
- node, err := New(conf)
- if err != nil {
- t.Fatalf("could not create a new node: %v", err)
- }
- // register dummy apis so we can test the modules are available and reachable with authentication
- node.RegisterAPIs([]rpc.API{
- {
- Namespace: "engine",
- Version: "1.0",
- Service: helloRPC("hello engine"),
- Public: true,
- Authenticated: true,
- },
- {
- Namespace: "eth",
- Version: "1.0",
- Service: helloRPC("hello eth"),
- Public: true,
- Authenticated: true,
- },
- })
- if err := node.Start(); err != nil {
- t.Fatalf("failed to start test node: %v", err)
- }
- defer node.Close()
-
- // sanity check we are running different endpoints
- if a, b := node.WSEndpoint(), node.WSAuthEndpoint(); a == b {
- t.Fatalf("expected ws and auth-ws endpoints to be different, got: %q and %q", a, b)
- }
- if a, b := node.HTTPEndpoint(), node.HTTPAuthEndpoint(); a == b {
- t.Fatalf("expected http and auth-http endpoints to be different, got: %q and %q", a, b)
- }
-
- goodAuth := NewJWTAuth(secret)
- var otherSecret [32]byte
- if _, err := crand.Read(otherSecret[:]); err != nil {
- t.Fatalf("failed to create jwt secret: %v", err)
- }
- badAuth := NewJWTAuth(otherSecret)
-
- notTooLong := time.Second * 57
- tooLong := time.Second * 60
- requestDelay := time.Second
-
- testCases := []authTest{
- // Auth works
- {name: "ws good", endpoint: node.WSAuthEndpoint(), prov: goodAuth, expectCall1Fail: false},
- {name: "http good", endpoint: node.HTTPAuthEndpoint(), prov: goodAuth, expectCall1Fail: false},
-
- // Try a bad auth
- {name: "ws bad", endpoint: node.WSAuthEndpoint(), prov: badAuth, expectDialFail: true}, // ws auth is immediate
- {name: "http bad", endpoint: node.HTTPAuthEndpoint(), prov: badAuth, expectCall1Fail: true}, // http auth is on first call
-
- // A common mistake with JWT is to allow the "none" algorithm, which is a valid JWT but not secure.
- {name: "ws none", endpoint: node.WSAuthEndpoint(), prov: noneAuth(secret), expectDialFail: true},
- {name: "http none", endpoint: node.HTTPAuthEndpoint(), prov: noneAuth(secret), expectCall1Fail: true},
-
- // claims of 5 seconds or more, older or newer, are not allowed
- {name: "ws too old", endpoint: node.WSAuthEndpoint(), prov: offsetTimeAuth(secret, -tooLong), expectDialFail: true},
- {name: "http too old", endpoint: node.HTTPAuthEndpoint(), prov: offsetTimeAuth(secret, -tooLong), expectCall1Fail: true},
- // note: for it to be too long we need to add a delay, so that once we receive the request, the difference has not dipped below the "tooLong"
- {name: "ws too new", endpoint: node.WSAuthEndpoint(), prov: offsetTimeAuth(secret, tooLong+requestDelay), expectDialFail: true},
- {name: "http too new", endpoint: node.HTTPAuthEndpoint(), prov: offsetTimeAuth(secret, tooLong+requestDelay), expectCall1Fail: true},
-
- // Try offset the time, but stay just within bounds
- {name: "ws old", endpoint: node.WSAuthEndpoint(), prov: offsetTimeAuth(secret, -notTooLong)},
- {name: "http old", endpoint: node.HTTPAuthEndpoint(), prov: offsetTimeAuth(secret, -notTooLong)},
- {name: "ws new", endpoint: node.WSAuthEndpoint(), prov: offsetTimeAuth(secret, notTooLong)},
- {name: "http new", endpoint: node.HTTPAuthEndpoint(), prov: offsetTimeAuth(secret, notTooLong)},
-
- // ws only authenticates on initial dial, then continues communication
- {name: "ws single auth", endpoint: node.WSAuthEndpoint(), prov: changingAuth(goodAuth, badAuth)},
- {name: "http call fail auth", endpoint: node.HTTPAuthEndpoint(), prov: changingAuth(goodAuth, badAuth), expectCall2Fail: true},
- {name: "http call fail time", endpoint: node.HTTPAuthEndpoint(), prov: changingAuth(goodAuth, offsetTimeAuth(secret, tooLong+requestDelay)), expectCall2Fail: true},
- }
-
- for _, testCase := range testCases {
- t.Run(testCase.name, testCase.Run)
- }
-}
-
-func noneAuth(secret [32]byte) rpc.HTTPAuth {
- return func(header http.Header) error {
- token := jwt.NewWithClaims(jwt.SigningMethodNone, jwt.MapClaims{
- "iat": &jwt.NumericDate{Time: time.Now()},
- })
- s, err := token.SignedString(secret[:])
- if err != nil {
- return fmt.Errorf("failed to create JWT token: %w", err)
- }
- header.Set("Authorization", "Bearer "+s)
- return nil
- }
-}
-
-func changingAuth(provs ...rpc.HTTPAuth) rpc.HTTPAuth {
- i := 0
- return func(header http.Header) error {
- i += 1
- if i > len(provs) {
- i = len(provs)
- }
- return provs[i-1](header)
- }
-}
-
-func offsetTimeAuth(secret [32]byte, offset time.Duration) rpc.HTTPAuth {
- return func(header http.Header) error {
- token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
- "iat": &jwt.NumericDate{Time: time.Now().Add(offset)},
- })
- s, err := token.SignedString(secret[:])
- if err != nil {
- return fmt.Errorf("failed to create JWT token: %w", err)
- }
- header.Set("Authorization", "Bearer "+s)
- return nil
- }
-}
diff --git a/node/node_example_test.go b/node/node_example_test.go
deleted file mode 100644
index e45ee49a25..0000000000
--- a/node/node_example_test.go
+++ /dev/null
@@ -1,59 +0,0 @@
-// 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 .
-
-package node_test
-
-import (
- "fmt"
- "log"
-
- "github.com/ethereum/go-ethereum/node"
-)
-
-// SampleLifecycle is a trivial network service that can be attached to a node for
-// life cycle management.
-//
-// The following methods are needed to implement a node.Lifecycle:
-// - Start() error - method invoked when the node is ready to start the service
-// - Stop() error - method invoked when the node terminates the service
-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 ExampleLifecycle() {
- // Create a network node to run protocols with the default values.
- stack, err := node.New(&node.Config{})
- if err != nil {
- log.Fatalf("Failed to create network node: %v", err)
- }
- defer stack.Close()
-
- // Create and register a simple network Lifecycle.
- service := new(SampleLifecycle)
- stack.RegisterLifecycle(service)
-
- // Boot up the entire protocol stack, do a restart and terminate
- if err := stack.Start(); err != nil {
- log.Fatalf("Failed to start the protocol stack: %v", err)
- }
- if err := stack.Close(); err != nil {
- log.Fatalf("Failed to stop the protocol stack: %v", err)
- }
- // Output:
- // Service starting...
- // Service stopping...
-}
diff --git a/node/node_test.go b/node/node_test.go
deleted file mode 100644
index 04810a815b..0000000000
--- a/node/node_test.go
+++ /dev/null
@@ -1,638 +0,0 @@
-// 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 .
-
-package node
-
-import (
- "errors"
- "fmt"
- "io"
- "net"
- "net/http"
- "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/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 := t.TempDir()
-
- // 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)
- defer node.Close()
- // 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, testMethod)
- 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, testMethod)
- 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,
- HTTPTimeouts: rpc.DefaultHTTPTimeouts,
- }
- node, err := New(conf)
- 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)
- }
- t.Cleanup(func() { resp.Body.Close() })
- 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
-}
diff --git a/node/rpcstack.go b/node/rpcstack.go
deleted file mode 100644
index b33c238051..0000000000
--- a/node/rpcstack.go
+++ /dev/null
@@ -1,644 +0,0 @@
-// Copyright 2020 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 .
-
-package node
-
-import (
- "compress/gzip"
- "context"
- "fmt"
- "io"
- "net"
- "net/http"
- "sort"
- "strconv"
- "strings"
- "sync"
- "sync/atomic"
- "time"
-
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/rpc"
- "github.com/rs/cors"
-)
-
-// httpConfig is the JSON-RPC/HTTP configuration.
-type httpConfig struct {
- Modules []string
- CorsAllowedOrigins []string
- Vhosts []string
- prefix string // path prefix on which to mount http handler
- rpcEndpointConfig
-}
-
-// wsConfig is the JSON-RPC/Websocket configuration
-type wsConfig struct {
- Origins []string
- Modules []string
- prefix string // path prefix on which to mount ws handler
- rpcEndpointConfig
-}
-
-type rpcEndpointConfig struct {
- jwtSecret []byte // optional JWT secret
- batchItemLimit int
- batchResponseSizeLimit int
-}
-
-type rpcHandler struct {
- http.Handler
- server *rpc.Server
-}
-
-type httpServer struct {
- log log.Logger
- timeouts rpc.HTTPTimeouts
- mux http.ServeMux // registered handlers go here
-
- mu sync.Mutex
- server *http.Server
- listener net.Listener // non-nil when server is running
-
- // HTTP RPC handler things.
-
- httpConfig httpConfig
- httpHandler atomic.Value // *rpcHandler
-
- // WebSocket handler things.
- wsConfig wsConfig
- wsHandler atomic.Value // *rpcHandler
-
- // These are set by setListenAddr.
- endpoint string
- host string
- port int
-
- handlerNames map[string]string
-}
-
-const (
- shutdownTimeout = 5 * time.Second
-)
-
-func newHTTPServer(log log.Logger, timeouts rpc.HTTPTimeouts) *httpServer {
- h := &httpServer{log: log, timeouts: timeouts, handlerNames: make(map[string]string)}
-
- h.httpHandler.Store((*rpcHandler)(nil))
- h.wsHandler.Store((*rpcHandler)(nil))
- return h
-}
-
-// setListenAddr configures the listening address of the server.
-// The address can only be set while the server isn't running.
-func (h *httpServer) setListenAddr(host string, port int) error {
- h.mu.Lock()
- defer h.mu.Unlock()
-
- if h.listener != nil && (host != h.host || port != h.port) {
- return fmt.Errorf("HTTP server already running on %s", h.endpoint)
- }
-
- h.host, h.port = host, port
- h.endpoint = net.JoinHostPort(host, fmt.Sprintf("%d", port))
- return nil
-}
-
-// listenAddr returns the listening address of the server.
-func (h *httpServer) listenAddr() string {
- h.mu.Lock()
- defer h.mu.Unlock()
-
- if h.listener != nil {
- return h.listener.Addr().String()
- }
- return h.endpoint
-}
-
-// start starts the HTTP server if it is enabled and not already running.
-func (h *httpServer) start() error {
- h.mu.Lock()
- defer h.mu.Unlock()
-
- if h.endpoint == "" || h.listener != nil {
- return nil // already running or not configured
- }
-
- // Initialize the server.
- h.server = &http.Server{Handler: h}
- if h.timeouts != (rpc.HTTPTimeouts{}) {
- CheckTimeouts(&h.timeouts)
- h.server.ReadTimeout = h.timeouts.ReadTimeout
- h.server.ReadHeaderTimeout = h.timeouts.ReadHeaderTimeout
- h.server.WriteTimeout = h.timeouts.WriteTimeout
- h.server.IdleTimeout = h.timeouts.IdleTimeout
- }
-
- // Start the server.
- listener, err := net.Listen("tcp", h.endpoint)
- if err != nil {
- // If the server fails to start, we need to clear out the RPC and WS
- // configuration so they can be configured another time.
- h.disableRPC()
- h.disableWS()
- return err
- }
- h.listener = listener
- go h.server.Serve(listener)
-
- if h.wsAllowed() {
- url := fmt.Sprintf("ws://%v", listener.Addr())
- if h.wsConfig.prefix != "" {
- url += h.wsConfig.prefix
- }
- h.log.Info("WebSocket enabled", "url", url)
- }
- // if server is websocket only, return after logging
- if !h.rpcAllowed() {
- return nil
- }
- // Log http endpoint.
- h.log.Info("HTTP server started",
- "endpoint", listener.Addr(), "auth", (h.httpConfig.jwtSecret != nil),
- "prefix", h.httpConfig.prefix,
- "cors", strings.Join(h.httpConfig.CorsAllowedOrigins, ","),
- "vhosts", strings.Join(h.httpConfig.Vhosts, ","),
- )
-
- // Log all handlers mounted on server.
- var paths []string
- for path := range h.handlerNames {
- paths = append(paths, path)
- }
- sort.Strings(paths)
- logged := make(map[string]bool, len(paths))
- for _, path := range paths {
- name := h.handlerNames[path]
- if !logged[name] {
- log.Info(name+" enabled", "url", "http://"+listener.Addr().String()+path)
- logged[name] = true
- }
- }
- return nil
-}
-
-func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- // check if ws request and serve if ws enabled
- ws := h.wsHandler.Load().(*rpcHandler)
- if ws != nil && isWebsocket(r) {
- if checkPath(r, h.wsConfig.prefix) {
- ws.ServeHTTP(w, r)
- }
- return
- }
-
- // if http-rpc is enabled, try to serve request
- rpc := h.httpHandler.Load().(*rpcHandler)
- if rpc != nil {
- // First try to route in the mux.
- // Requests to a path below root are handled by the mux,
- // which has all the handlers registered via Node.RegisterHandler.
- // These are made available when RPC is enabled.
- muxHandler, pattern := h.mux.Handler(r)
- if pattern != "" {
- muxHandler.ServeHTTP(w, r)
- return
- }
-
- if checkPath(r, h.httpConfig.prefix) {
- rpc.ServeHTTP(w, r)
- return
- }
- }
- w.WriteHeader(http.StatusNotFound)
-}
-
-// checkPath checks whether a given request URL matches a given path prefix.
-func checkPath(r *http.Request, path string) bool {
- // if no prefix has been specified, request URL must be on root
- if path == "" {
- return r.URL.Path == "/"
- }
- // otherwise, check to make sure prefix matches
- return len(r.URL.Path) >= len(path) && r.URL.Path[:len(path)] == path
-}
-
-// validatePrefix checks if 'path' is a valid configuration value for the RPC prefix option.
-func validatePrefix(what, path string) error {
- if path == "" {
- return nil
- }
- if path[0] != '/' {
- return fmt.Errorf(`%s RPC path prefix %q does not contain leading "/"`, what, path)
- }
- if strings.ContainsAny(path, "?#") {
- // This is just to avoid confusion. While these would match correctly (i.e. they'd
- // match if URL-escaped into path), it's not easy to understand for users when
- // setting that on the command line.
- return fmt.Errorf("%s RPC path prefix %q contains URL meta-characters", what, path)
- }
- return nil
-}
-
-// stop shuts down the HTTP server.
-func (h *httpServer) stop() {
- h.mu.Lock()
- defer h.mu.Unlock()
- h.doStop()
-}
-
-func (h *httpServer) doStop() {
- if h.listener == nil {
- return // not running
- }
-
- // Shut down the server.
- httpHandler := h.httpHandler.Load().(*rpcHandler)
- wsHandler := h.wsHandler.Load().(*rpcHandler)
- if httpHandler != nil {
- h.httpHandler.Store((*rpcHandler)(nil))
- httpHandler.server.Stop()
- }
- if wsHandler != nil {
- h.wsHandler.Store((*rpcHandler)(nil))
- wsHandler.server.Stop()
- }
-
- ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
- defer cancel()
- err := h.server.Shutdown(ctx)
- if err != nil && err == ctx.Err() {
- h.log.Warn("HTTP server graceful shutdown timed out")
- h.server.Close()
- }
-
- h.listener.Close()
- h.log.Info("HTTP server stopped", "endpoint", h.listener.Addr())
-
- // Clear out everything to allow re-configuring it later.
- h.host, h.port, h.endpoint = "", 0, ""
- h.server, h.listener = nil, nil
-}
-
-// enableRPC turns on JSON-RPC over HTTP on the server.
-func (h *httpServer) enableRPC(apis []rpc.API, config httpConfig) error {
- h.mu.Lock()
- defer h.mu.Unlock()
-
- if h.rpcAllowed() {
- return fmt.Errorf("JSON-RPC over HTTP is already enabled")
- }
-
- // Create RPC server and handler.
- srv := rpc.NewServer()
- srv.SetBatchLimits(config.batchItemLimit, config.batchResponseSizeLimit)
- if err := RegisterApis(apis, config.Modules, srv); err != nil {
- return err
- }
- h.httpConfig = config
- h.httpHandler.Store(&rpcHandler{
- Handler: NewHTTPHandlerStack(srv, config.CorsAllowedOrigins, config.Vhosts, config.jwtSecret),
- server: srv,
- })
- return nil
-}
-
-// disableRPC stops the HTTP RPC handler. This is internal, the caller must hold h.mu.
-func (h *httpServer) disableRPC() bool {
- handler := h.httpHandler.Load().(*rpcHandler)
- if handler != nil {
- h.httpHandler.Store((*rpcHandler)(nil))
- handler.server.Stop()
- }
- return handler != nil
-}
-
-// enableWS turns on JSON-RPC over WebSocket on the server.
-func (h *httpServer) enableWS(apis []rpc.API, config wsConfig) error {
- h.mu.Lock()
- defer h.mu.Unlock()
-
- if h.wsAllowed() {
- return fmt.Errorf("JSON-RPC over WebSocket is already enabled")
- }
- // Create RPC server and handler.
- srv := rpc.NewServer()
- srv.SetBatchLimits(config.batchItemLimit, config.batchResponseSizeLimit)
- if err := RegisterApis(apis, config.Modules, srv); err != nil {
- return err
- }
- h.wsConfig = config
- h.wsHandler.Store(&rpcHandler{
- Handler: NewWSHandlerStack(srv.WebsocketHandler(config.Origins), config.jwtSecret),
- server: srv,
- })
- return nil
-}
-
-// stopWS disables JSON-RPC over WebSocket and also stops the server if it only serves WebSocket.
-func (h *httpServer) stopWS() {
- h.mu.Lock()
- defer h.mu.Unlock()
-
- if h.disableWS() {
- if !h.rpcAllowed() {
- h.doStop()
- }
- }
-}
-
-// disableWS disables the WebSocket handler. This is internal, the caller must hold h.mu.
-func (h *httpServer) disableWS() bool {
- ws := h.wsHandler.Load().(*rpcHandler)
- if ws != nil {
- h.wsHandler.Store((*rpcHandler)(nil))
- ws.server.Stop()
- }
- return ws != nil
-}
-
-// rpcAllowed returns true when JSON-RPC over HTTP is enabled.
-func (h *httpServer) rpcAllowed() bool {
- return h.httpHandler.Load().(*rpcHandler) != nil
-}
-
-// wsAllowed returns true when JSON-RPC over WebSocket is enabled.
-func (h *httpServer) wsAllowed() bool {
- return h.wsHandler.Load().(*rpcHandler) != nil
-}
-
-// isWebsocket checks the header of an http request for a websocket upgrade request.
-func isWebsocket(r *http.Request) bool {
- return strings.EqualFold(r.Header.Get("Upgrade"), "websocket") &&
- strings.Contains(strings.ToLower(r.Header.Get("Connection")), "upgrade")
-}
-
-// NewHTTPHandlerStack returns wrapped http-related handlers
-func NewHTTPHandlerStack(srv http.Handler, cors []string, vhosts []string, jwtSecret []byte) http.Handler {
- // Wrap the CORS-handler within a host-handler
- handler := newCorsHandler(srv, cors)
- handler = newVHostHandler(vhosts, handler)
- if len(jwtSecret) != 0 {
- handler = newJWTHandler(jwtSecret, handler)
- }
- return newGzipHandler(handler)
-}
-
-// NewWSHandlerStack returns a wrapped ws-related handler.
-func NewWSHandlerStack(srv http.Handler, jwtSecret []byte) http.Handler {
- if len(jwtSecret) != 0 {
- return newJWTHandler(jwtSecret, srv)
- }
- return srv
-}
-
-func newCorsHandler(srv http.Handler, allowedOrigins []string) http.Handler {
- // disable CORS support if user has not specified a custom CORS configuration
- if len(allowedOrigins) == 0 {
- return srv
- }
- c := cors.New(cors.Options{
- AllowedOrigins: allowedOrigins,
- AllowedMethods: []string{http.MethodPost, http.MethodGet},
- AllowedHeaders: []string{"*"},
- MaxAge: 600,
- })
- return c.Handler(srv)
-}
-
-// virtualHostHandler is a handler which validates the Host-header of incoming requests.
-// Using virtual hosts can help prevent DNS rebinding attacks, where a 'random' domain name points to
-// the service ip address (but without CORS headers). By verifying the targeted virtual host, we can
-// ensure that it's a destination that the node operator has defined.
-type virtualHostHandler struct {
- vhosts map[string]struct{}
- next http.Handler
-}
-
-func newVHostHandler(vhosts []string, next http.Handler) http.Handler {
- vhostMap := make(map[string]struct{})
- for _, allowedHost := range vhosts {
- vhostMap[strings.ToLower(allowedHost)] = struct{}{}
- }
- return &virtualHostHandler{vhostMap, next}
-}
-
-// ServeHTTP serves JSON-RPC requests over HTTP, implements http.Handler
-func (h *virtualHostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- // if r.Host is not set, we can continue serving since a browser would set the Host header
- if r.Host == "" {
- h.next.ServeHTTP(w, r)
- return
- }
- host, _, err := net.SplitHostPort(r.Host)
- if err != nil {
- // Either invalid (too many colons) or no port specified
- host = r.Host
- }
- if ipAddr := net.ParseIP(host); ipAddr != nil {
- // It's an IP address, we can serve that
- h.next.ServeHTTP(w, r)
- return
- }
- // Not an IP address, but a hostname. Need to validate
- if _, exist := h.vhosts["*"]; exist {
- h.next.ServeHTTP(w, r)
- return
- }
- if _, exist := h.vhosts[host]; exist {
- h.next.ServeHTTP(w, r)
- return
- }
- http.Error(w, "invalid host specified", http.StatusForbidden)
-}
-
-var gzPool = sync.Pool{
- New: func() interface{} {
- w := gzip.NewWriter(io.Discard)
- return w
- },
-}
-
-type gzipResponseWriter struct {
- resp http.ResponseWriter
-
- gz *gzip.Writer
- contentLength uint64 // total length of the uncompressed response
- written uint64 // amount of written bytes from the uncompressed response
- hasLength bool // true if uncompressed response had Content-Length
- inited bool // true after init was called for the first time
-}
-
-// init runs just before response headers are written. Among other things, this function
-// also decides whether compression will be applied at all.
-func (w *gzipResponseWriter) init() {
- if w.inited {
- return
- }
- w.inited = true
-
- hdr := w.resp.Header()
- length := hdr.Get("content-length")
- if len(length) > 0 {
- if n, err := strconv.ParseUint(length, 10, 64); err != nil {
- w.hasLength = true
- w.contentLength = n
- }
- }
-
- // Setting Transfer-Encoding to "identity" explicitly disables compression. net/http
- // also recognizes this header value and uses it to disable "chunked" transfer
- // encoding, trimming the header from the response. This means downstream handlers can
- // set this without harm, even if they aren't wrapped by newGzipHandler.
- //
- // In go-ethereum, we use this signal to disable compression for certain error
- // responses which are flushed out close to the write deadline of the response. For
- // these cases, we want to avoid chunked transfer encoding and compression because
- // they require additional output that may not get written in time.
- passthrough := hdr.Get("transfer-encoding") == "identity"
- if !passthrough {
- w.gz = gzPool.Get().(*gzip.Writer)
- w.gz.Reset(w.resp)
- hdr.Del("content-length")
- hdr.Set("content-encoding", "gzip")
- }
-}
-
-func (w *gzipResponseWriter) Header() http.Header {
- return w.resp.Header()
-}
-
-func (w *gzipResponseWriter) WriteHeader(status int) {
- w.init()
- w.resp.WriteHeader(status)
-}
-
-func (w *gzipResponseWriter) Write(b []byte) (int, error) {
- w.init()
-
- if w.gz == nil {
- // Compression is disabled.
- return w.resp.Write(b)
- }
-
- n, err := w.gz.Write(b)
- w.written += uint64(n)
- if w.hasLength && w.written >= w.contentLength {
- // The HTTP handler has finished writing the entire uncompressed response. Close
- // the gzip stream to ensure the footer will be seen by the client in case the
- // response is flushed after this call to write.
- err = w.gz.Close()
- }
- return n, err
-}
-
-func (w *gzipResponseWriter) Flush() {
- if w.gz != nil {
- w.gz.Flush()
- }
- if f, ok := w.resp.(http.Flusher); ok {
- f.Flush()
- }
-}
-
-func (w *gzipResponseWriter) close() {
- if w.gz == nil {
- return
- }
- w.gz.Close()
- gzPool.Put(w.gz)
- w.gz = nil
-}
-
-func newGzipHandler(next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
- next.ServeHTTP(w, r)
- return
- }
-
- wrapper := &gzipResponseWriter{resp: w}
- defer wrapper.close()
-
- next.ServeHTTP(wrapper, r)
- })
-}
-
-type ipcServer struct {
- log log.Logger
- endpoint string
-
- mu sync.Mutex
- listener net.Listener
- srv *rpc.Server
-}
-
-func newIPCServer(log log.Logger, endpoint string) *ipcServer {
- return &ipcServer{log: log, endpoint: endpoint}
-}
-
-// Start starts the httpServer's http.Server
-func (is *ipcServer) start(apis []rpc.API) error {
- is.mu.Lock()
- defer is.mu.Unlock()
-
- if is.listener != nil {
- return nil // already running
- }
- listener, srv, err := rpc.StartIPCEndpoint(is.endpoint, apis)
- if err != nil {
- is.log.Warn("IPC opening failed", "url", is.endpoint, "error", err)
- return err
- }
- is.log.Info("IPC endpoint opened", "url", is.endpoint)
- is.listener, is.srv = listener, srv
- return nil
-}
-
-func (is *ipcServer) stop() error {
- is.mu.Lock()
- defer is.mu.Unlock()
-
- if is.listener == nil {
- return nil // not running
- }
- err := is.listener.Close()
- is.srv.Stop()
- is.listener, is.srv = nil, nil
- is.log.Info("IPC endpoint closed", "url", is.endpoint)
- return err
-}
-
-// RegisterApis checks the given modules' availability, generates an allowlist based on the allowed modules,
-// and then registers all of the APIs exposed by the services.
-func RegisterApis(apis []rpc.API, modules []string, srv *rpc.Server) error {
- if bad, available := checkModuleAvailability(modules, apis); len(bad) > 0 {
- log.Error("Unavailable modules in HTTP API list", "unavailable", bad, "available", available)
- }
- // Generate the allow list based on the allowed modules
- allowList := make(map[string]bool)
- for _, module := range modules {
- allowList[module] = true
- }
- // Register all the APIs exposed by the services
- for _, api := range apis {
- if allowList[api.Namespace] || len(allowList) == 0 {
- if err := srv.RegisterName(api.Namespace, api.Service); err != nil {
- return err
- }
- }
- }
- return nil
-}
diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go
deleted file mode 100644
index e41cc51ad3..0000000000
--- a/node/rpcstack_test.go
+++ /dev/null
@@ -1,616 +0,0 @@
-// Copyright 2020 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 .
-
-package node
-
-import (
- "bytes"
- "fmt"
- "io"
- "net/http"
- "net/http/httptest"
- "net/url"
- "strconv"
- "strings"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/internal/testlog"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/rpc"
- "github.com/golang-jwt/jwt/v4"
- "github.com/gorilla/websocket"
- "github.com/stretchr/testify/assert"
-)
-
-const testMethod = "rpc_modules"
-
-// TestCorsHandler makes sure CORS are properly handled on the http server.
-func TestCorsHandler(t *testing.T) {
- srv := createAndStartServer(t, &httpConfig{CorsAllowedOrigins: []string{"test", "test.com"}}, false, &wsConfig{}, nil)
- defer srv.stop()
- url := "http://" + srv.listenAddr()
-
- resp := rpcRequest(t, url, testMethod, "origin", "test.com")
- assert.Equal(t, "test.com", resp.Header.Get("Access-Control-Allow-Origin"))
-
- resp2 := rpcRequest(t, url, testMethod, "origin", "bad")
- assert.Equal(t, "", resp2.Header.Get("Access-Control-Allow-Origin"))
-}
-
-// TestVhosts makes sure vhosts are properly handled on the http server.
-func TestVhosts(t *testing.T) {
- srv := createAndStartServer(t, &httpConfig{Vhosts: []string{"test"}}, false, &wsConfig{}, nil)
- defer srv.stop()
- url := "http://" + srv.listenAddr()
-
- resp := rpcRequest(t, url, testMethod, "host", "test")
- assert.Equal(t, resp.StatusCode, http.StatusOK)
-
- resp2 := rpcRequest(t, url, testMethod, "host", "bad")
- assert.Equal(t, resp2.StatusCode, http.StatusForbidden)
-}
-
-type originTest struct {
- spec string
- expOk []string
- expFail []string
-}
-
-// splitAndTrim splits input separated by a comma
-// and trims excessive white space from the substrings.
-// Copied over from flags.go
-func splitAndTrim(input string) (ret []string) {
- l := strings.Split(input, ",")
- for _, r := range l {
- r = strings.TrimSpace(r)
- if len(r) > 0 {
- ret = append(ret, r)
- }
- }
- return ret
-}
-
-// TestWebsocketOrigins makes sure the websocket origins are properly handled on the websocket server.
-func TestWebsocketOrigins(t *testing.T) {
- tests := []originTest{
- {
- spec: "*", // allow all
- expOk: []string{"", "http://test", "https://test", "http://test:8540", "https://test:8540",
- "http://test.com", "https://foo.test", "http://testa", "http://atestb:8540", "https://atestb:8540"},
- },
- {
- spec: "test",
- expOk: []string{"http://test", "https://test", "http://test:8540", "https://test:8540"},
- expFail: []string{"http://test.com", "https://foo.test", "http://testa", "http://atestb:8540", "https://atestb:8540"},
- },
- // scheme tests
- {
- spec: "https://test",
- expOk: []string{"https://test", "https://test:9999"},
- expFail: []string{
- "test", // no scheme, required by spec
- "http://test", // wrong scheme
- "http://test.foo", "https://a.test.x", // subdomain variations
- "http://testx:8540", "https://xtest:8540"},
- },
- // ip tests
- {
- spec: "https://12.34.56.78",
- expOk: []string{"https://12.34.56.78", "https://12.34.56.78:8540"},
- expFail: []string{
- "http://12.34.56.78", // wrong scheme
- "http://12.34.56.78:443", // wrong scheme
- "http://1.12.34.56.78", // wrong 'domain name'
- "http://12.34.56.78.a", // wrong 'domain name'
- "https://87.65.43.21", "http://87.65.43.21:8540", "https://87.65.43.21:8540"},
- },
- // port tests
- {
- spec: "test:8540",
- expOk: []string{"http://test:8540", "https://test:8540"},
- expFail: []string{
- "http://test", "https://test", // spec says port required
- "http://test:8541", "https://test:8541", // wrong port
- "http://bad", "https://bad", "http://bad:8540", "https://bad:8540"},
- },
- // scheme and port
- {
- spec: "https://test:8540",
- expOk: []string{"https://test:8540"},
- expFail: []string{
- "https://test", // missing port
- "http://test", // missing port, + wrong scheme
- "http://test:8540", // wrong scheme
- "http://test:8541", "https://test:8541", // wrong port
- "http://bad", "https://bad", "http://bad:8540", "https://bad:8540"},
- },
- // several allowed origins
- {
- spec: "localhost,http://127.0.0.1",
- expOk: []string{"localhost", "http://localhost", "https://localhost:8443",
- "http://127.0.0.1", "http://127.0.0.1:8080"},
- expFail: []string{
- "https://127.0.0.1", // wrong scheme
- "http://bad", "https://bad", "http://bad:8540", "https://bad:8540"},
- },
- }
- for _, tc := range tests {
- srv := createAndStartServer(t, &httpConfig{}, true, &wsConfig{Origins: splitAndTrim(tc.spec)}, nil)
- url := fmt.Sprintf("ws://%v", srv.listenAddr())
- for _, origin := range tc.expOk {
- if err := wsRequest(t, url, "Origin", origin); err != nil {
- t.Errorf("spec '%v', origin '%v': expected ok, got %v", tc.spec, origin, err)
- }
- }
- for _, origin := range tc.expFail {
- if err := wsRequest(t, url, "Origin", origin); err == nil {
- t.Errorf("spec '%v', origin '%v': expected not to allow, got ok", tc.spec, origin)
- }
- }
- srv.stop()
- }
-}
-
-// TestIsWebsocket tests if an incoming websocket upgrade request is handled properly.
-func TestIsWebsocket(t *testing.T) {
- r, _ := http.NewRequest(http.MethodGet, "/", nil)
-
- assert.False(t, isWebsocket(r))
- r.Header.Set("upgrade", "websocket")
- assert.False(t, isWebsocket(r))
- r.Header.Set("connection", "upgrade")
- assert.True(t, isWebsocket(r))
- r.Header.Set("connection", "upgrade,keep-alive")
- assert.True(t, isWebsocket(r))
- r.Header.Set("connection", " UPGRADE,keep-alive")
- assert.True(t, isWebsocket(r))
-}
-
-func Test_checkPath(t *testing.T) {
- tests := []struct {
- req *http.Request
- prefix string
- expected bool
- }{
- {
- req: &http.Request{URL: &url.URL{Path: "/test"}},
- prefix: "/test",
- expected: true,
- },
- {
- req: &http.Request{URL: &url.URL{Path: "/testing"}},
- prefix: "/test",
- expected: true,
- },
- {
- req: &http.Request{URL: &url.URL{Path: "/"}},
- prefix: "/test",
- expected: false,
- },
- {
- req: &http.Request{URL: &url.URL{Path: "/fail"}},
- prefix: "/test",
- expected: false,
- },
- {
- req: &http.Request{URL: &url.URL{Path: "/"}},
- prefix: "",
- expected: true,
- },
- {
- req: &http.Request{URL: &url.URL{Path: "/fail"}},
- prefix: "",
- expected: false,
- },
- {
- req: &http.Request{URL: &url.URL{Path: "/"}},
- prefix: "/",
- expected: true,
- },
- {
- req: &http.Request{URL: &url.URL{Path: "/testing"}},
- prefix: "/",
- expected: true,
- },
- }
-
- for i, tt := range tests {
- t.Run(strconv.Itoa(i), func(t *testing.T) {
- assert.Equal(t, tt.expected, checkPath(tt.req, tt.prefix))
- })
- }
-}
-
-func createAndStartServer(t *testing.T, conf *httpConfig, ws bool, wsConf *wsConfig, timeouts *rpc.HTTPTimeouts) *httpServer {
- t.Helper()
-
- if timeouts == nil {
- timeouts = &rpc.DefaultHTTPTimeouts
- }
- srv := newHTTPServer(testlog.Logger(t, log.LvlDebug), *timeouts)
- assert.NoError(t, srv.enableRPC(apis(), *conf))
- if ws {
- assert.NoError(t, srv.enableWS(nil, *wsConf))
- }
- assert.NoError(t, srv.setListenAddr("localhost", 0))
- assert.NoError(t, srv.start())
- return srv
-}
-
-// wsRequest attempts to open a WebSocket connection to the given URL.
-func wsRequest(t *testing.T, url string, extraHeaders ...string) error {
- t.Helper()
- //t.Logf("checking WebSocket on %s (origin %q)", url, browserOrigin)
-
- headers := make(http.Header)
- // Apply extra headers.
- if len(extraHeaders)%2 != 0 {
- panic("odd extraHeaders length")
- }
- for i := 0; i < len(extraHeaders); i += 2 {
- key, value := extraHeaders[i], extraHeaders[i+1]
- headers.Set(key, value)
- }
- conn, _, err := websocket.DefaultDialer.Dial(url, headers)
- if conn != nil {
- conn.Close()
- }
- return err
-}
-
-// rpcRequest performs a JSON-RPC request to the given URL.
-func rpcRequest(t *testing.T, url, method string, extraHeaders ...string) *http.Response {
- t.Helper()
-
- body := fmt.Sprintf(`{"jsonrpc":"2.0","id":1,"method":"%s","params":[]}`, method)
- return baseRpcRequest(t, url, body, extraHeaders...)
-}
-
-func batchRpcRequest(t *testing.T, url string, methods []string, extraHeaders ...string) *http.Response {
- reqs := make([]string, len(methods))
- for i, m := range methods {
- reqs[i] = fmt.Sprintf(`{"jsonrpc":"2.0","id":1,"method":"%s","params":[]}`, m)
- }
- body := fmt.Sprintf(`[%s]`, strings.Join(reqs, ","))
- return baseRpcRequest(t, url, body, extraHeaders...)
-}
-
-func baseRpcRequest(t *testing.T, url, bodyStr string, extraHeaders ...string) *http.Response {
- t.Helper()
-
- // Create the request.
- body := bytes.NewReader([]byte(bodyStr))
- req, err := http.NewRequest(http.MethodPost, url, body)
- if err != nil {
- t.Fatal("could not create http request:", err)
- }
- req.Header.Set("content-type", "application/json")
- req.Header.Set("accept-encoding", "identity")
-
- // Apply extra headers.
- if len(extraHeaders)%2 != 0 {
- panic("odd extraHeaders length")
- }
- for i := 0; i < len(extraHeaders); i += 2 {
- key, value := extraHeaders[i], extraHeaders[i+1]
- if strings.EqualFold(key, "host") {
- req.Host = value
- } else {
- req.Header.Set(key, value)
- }
- }
-
- // Perform the request.
- t.Logf("checking RPC/HTTP on %s %v", url, extraHeaders)
- resp, err := http.DefaultClient.Do(req)
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() { resp.Body.Close() })
- return resp
-}
-
-type testClaim map[string]interface{}
-
-func (testClaim) Valid() error {
- return nil
-}
-
-func TestJWT(t *testing.T) {
- var secret = []byte("secret")
- issueToken := func(secret []byte, method jwt.SigningMethod, input map[string]interface{}) string {
- if method == nil {
- method = jwt.SigningMethodHS256
- }
- ss, _ := jwt.NewWithClaims(method, testClaim(input)).SignedString(secret)
- return ss
- }
- cfg := rpcEndpointConfig{jwtSecret: []byte("secret")}
- httpcfg := &httpConfig{rpcEndpointConfig: cfg}
- wscfg := &wsConfig{Origins: []string{"*"}, rpcEndpointConfig: cfg}
- srv := createAndStartServer(t, httpcfg, true, wscfg, nil)
- wsUrl := fmt.Sprintf("ws://%v", srv.listenAddr())
- htUrl := fmt.Sprintf("http://%v", srv.listenAddr())
-
- expOk := []func() string{
- func() string {
- return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()}))
- },
- func() string {
- return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix() + 4}))
- },
- func() string {
- return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix() - 4}))
- },
- func() string {
- return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{
- "iat": time.Now().Unix(),
- "exp": time.Now().Unix() + 2,
- }))
- },
- func() string {
- return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{
- "iat": time.Now().Unix(),
- "bar": "baz",
- }))
- },
- }
- for i, tokenFn := range expOk {
- token := tokenFn()
- if err := wsRequest(t, wsUrl, "Authorization", token); err != nil {
- t.Errorf("test %d-ws, token '%v': expected ok, got %v", i, token, err)
- }
- token = tokenFn()
- if resp := rpcRequest(t, htUrl, testMethod, "Authorization", token); resp.StatusCode != 200 {
- t.Errorf("test %d-http, token '%v': expected ok, got %v", i, token, resp.StatusCode)
- }
- }
-
- expFail := []func() string{
- // future
- func() string {
- return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix() + int64(jwtExpiryTimeout.Seconds()) + 1}))
- },
- // stale
- func() string {
- return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix() - int64(jwtExpiryTimeout.Seconds()) - 1}))
- },
- // wrong algo
- func() string {
- return fmt.Sprintf("Bearer %v", issueToken(secret, jwt.SigningMethodHS512, testClaim{"iat": time.Now().Unix() + 4}))
- },
- // expired
- func() string {
- return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix(), "exp": time.Now().Unix()}))
- },
- // missing mandatory iat
- func() string {
- return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{}))
- },
- // wrong secret
- func() string {
- return fmt.Sprintf("Bearer %v", issueToken([]byte("wrong"), nil, testClaim{"iat": time.Now().Unix()}))
- },
- func() string {
- return fmt.Sprintf("Bearer %v", issueToken([]byte{}, nil, testClaim{"iat": time.Now().Unix()}))
- },
- func() string {
- return fmt.Sprintf("Bearer %v", issueToken(nil, nil, testClaim{"iat": time.Now().Unix()}))
- },
- // Various malformed syntax
- func() string {
- return fmt.Sprintf("%v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()}))
- },
- func() string {
- return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()}))
- },
- func() string {
- return fmt.Sprintf("bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()}))
- },
- func() string {
- return fmt.Sprintf("Bearer: %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()}))
- },
- func() string {
- return fmt.Sprintf("Bearer:%v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()}))
- },
- func() string {
- return fmt.Sprintf("Bearer\t%v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()}))
- },
- func() string {
- return fmt.Sprintf("Bearer \t%v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()}))
- },
- }
- for i, tokenFn := range expFail {
- token := tokenFn()
- if err := wsRequest(t, wsUrl, "Authorization", token); err == nil {
- t.Errorf("tc %d-ws, token '%v': expected not to allow, got ok", i, token)
- }
-
- token = tokenFn()
- resp := rpcRequest(t, htUrl, testMethod, "Authorization", token)
- if resp.StatusCode != http.StatusUnauthorized {
- t.Errorf("tc %d-http, token '%v': expected not to allow, got %v", i, token, resp.StatusCode)
- }
- }
- srv.stop()
-}
-
-func TestGzipHandler(t *testing.T) {
- type gzipTest struct {
- name string
- handler http.HandlerFunc
- status int
- isGzip bool
- header map[string]string
- }
- tests := []gzipTest{
- {
- name: "Write",
- handler: func(w http.ResponseWriter, r *http.Request) {
- w.Write([]byte("response"))
- },
- isGzip: true,
- status: 200,
- },
- {
- name: "WriteHeader",
- handler: func(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("x-foo", "bar")
- w.WriteHeader(205)
- w.Write([]byte("response"))
- },
- isGzip: true,
- status: 205,
- header: map[string]string{"x-foo": "bar"},
- },
- {
- name: "WriteContentLength",
- handler: func(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("content-length", "8")
- w.Write([]byte("response"))
- },
- isGzip: true,
- status: 200,
- },
- {
- name: "Flush",
- handler: func(w http.ResponseWriter, r *http.Request) {
- w.Write([]byte("res"))
- w.(http.Flusher).Flush()
- w.Write([]byte("ponse"))
- },
- isGzip: true,
- status: 200,
- },
- {
- name: "disable",
- handler: func(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("transfer-encoding", "identity")
- w.Header().Set("x-foo", "bar")
- w.Write([]byte("response"))
- },
- isGzip: false,
- status: 200,
- header: map[string]string{"x-foo": "bar"},
- },
- {
- name: "disable-WriteHeader",
- handler: func(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("transfer-encoding", "identity")
- w.Header().Set("x-foo", "bar")
- w.WriteHeader(205)
- w.Write([]byte("response"))
- },
- isGzip: false,
- status: 205,
- header: map[string]string{"x-foo": "bar"},
- },
- }
-
- for _, test := range tests {
- test := test
- t.Run(test.name, func(t *testing.T) {
- srv := httptest.NewServer(newGzipHandler(test.handler))
- defer srv.Close()
-
- resp, err := http.Get(srv.URL)
- if err != nil {
- t.Fatal(err)
- }
- defer resp.Body.Close()
-
- content, err := io.ReadAll(resp.Body)
- if err != nil {
- t.Fatal(err)
- }
- wasGzip := resp.Uncompressed
-
- if string(content) != "response" {
- t.Fatalf("wrong response content %q", content)
- }
- if wasGzip != test.isGzip {
- t.Fatalf("response gzipped == %t, want %t", wasGzip, test.isGzip)
- }
- if resp.StatusCode != test.status {
- t.Fatalf("response status == %d, want %d", resp.StatusCode, test.status)
- }
- for name, expectedValue := range test.header {
- if v := resp.Header.Get(name); v != expectedValue {
- t.Fatalf("response header %s == %s, want %s", name, v, expectedValue)
- }
- }
- })
- }
-}
-
-func TestHTTPWriteTimeout(t *testing.T) {
- const (
- timeoutRes = `{"jsonrpc":"2.0","id":1,"error":{"code":-32002,"message":"request timed out"}}`
- greetRes = `{"jsonrpc":"2.0","id":1,"result":"Hello"}`
- )
- // Set-up server
- timeouts := rpc.DefaultHTTPTimeouts
- timeouts.WriteTimeout = time.Second
- srv := createAndStartServer(t, &httpConfig{Modules: []string{"test"}}, false, &wsConfig{}, &timeouts)
- url := fmt.Sprintf("http://%v", srv.listenAddr())
-
- // Send normal request
- t.Run("message", func(t *testing.T) {
- resp := rpcRequest(t, url, "test_sleep")
- defer resp.Body.Close()
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- t.Fatal(err)
- }
- if string(body) != timeoutRes {
- t.Errorf("wrong response. have %s, want %s", string(body), timeoutRes)
- }
- })
-
- // Batch request
- t.Run("batch", func(t *testing.T) {
- want := fmt.Sprintf("[%s,%s,%s]", greetRes, timeoutRes, timeoutRes)
- resp := batchRpcRequest(t, url, []string{"test_greet", "test_sleep", "test_greet"})
- defer resp.Body.Close()
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- t.Fatal(err)
- }
- if string(body) != want {
- t.Errorf("wrong response. have %s, want %s", string(body), want)
- }
- })
-}
-
-func apis() []rpc.API {
- return []rpc.API{
- {
- Namespace: "test",
- Service: &testService{},
- },
- }
-}
-
-type testService struct{}
-
-func (s *testService) Greet() string {
- return "Hello"
-}
-
-func (s *testService) Sleep() {
- time.Sleep(1500 * time.Millisecond)
-}
diff --git a/node/utils_test.go b/node/utils_test.go
deleted file mode 100644
index 681f3a8b28..0000000000
--- a/node/utils_test.go
+++ /dev/null
@@ -1,106 +0,0 @@
-// 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 .
-
-// Contains a batch of utility type declarations used by the tests. As the node
-// operates on unique types, a lot of them are needed to check various features.
-
-package node
-
-import (
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/rpc"
-)
-
-// 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() *Noop {
- noop := new(Noop)
- return noop
-}
-
-// Set of services all wrapping the base NoopLifecycle resulting in the same method
-// signatures but different outer types.
-type Noop struct{ NoopLifecycle }
-
-// 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 {
- start error
- stop error
-
- startHook func()
- stopHook func()
-}
-
-func (s *InstrumentedService) Start() error {
- if s.startHook != nil {
- s.startHook()
- }
- return s.start
-}
-
-func (s *InstrumentedService) Stop() error {
- if s.stopHook != nil {
- s.stopHook()
- }
- return s.stop
-}
-
-type FullService struct{}
-
-func NewFullService(stack *Node) (*FullService, error) {
- fs := new(FullService)
-
- stack.RegisterProtocols(fs.Protocols())
- stack.RegisterAPIs(fs.APIs())
- stack.RegisterLifecycle(fs)
- return fs, nil
-}
-
-func (f *FullService) Start() error { return nil }
-
-func (f *FullService) Stop() error { return nil }
-
-func (f *FullService) Protocols() []p2p.Protocol {
- return []p2p.Protocol{
- {
- Name: "test1",
- Version: uint(1),
- },
- {
- Name: "test2",
- Version: uint(2),
- },
- }
-}
-
-func (f *FullService) APIs() []rpc.API {
- return []rpc.API{
- {
- Namespace: "admin",
- },
- {
- Namespace: "debug",
- },
- {
- Namespace: "net",
- },
- }
-}