353
vendor/github.com/ethereum/go-ethereum/node/api.go
generated
vendored
Normal file
353
vendor/github.com/ethereum/go-ethereum/node/api.go
generated
vendored
Normal file
@@ -0,0 +1,353 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package node
|
||||
|
||||
import (
|
||||
"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"
|
||||
)
|
||||
|
||||
// PrivateAdminAPI is the collection of administrative API methods exposed only
|
||||
// over a secure RPC channel.
|
||||
type PrivateAdminAPI struct {
|
||||
node *Node // Node interfaced by this API
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// DeletePeer disconnects and deletes forcefully a remote node.
|
||||
func (api *PrivateAdminAPI) DeletePeer(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.ParseV4(url)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid enode: %v", err)
|
||||
}
|
||||
if err := server.DeletePeer(node); err != nil {
|
||||
return false, err
|
||||
}
|
||||
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,
|
||||
}
|
||||
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,
|
||||
}
|
||||
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)
|
||||
}
|
||||
468
vendor/github.com/ethereum/go-ethereum/node/config.go
generated
vendored
Normal file
468
vendor/github.com/ethereum/go-ethereum/node/config.go
generated
vendored
Normal file
@@ -0,0 +1,468 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package node
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"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"`
|
||||
|
||||
// JWTSecret is the path to the hex-encoded jwt secret.
|
||||
JWTSecret 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 fmt.Sprintf("%s:%d", c.HTTPHost, 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 fmt.Sprintf("%s:%d", c.WSHost, 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 getKeyStoreDir(conf *Config) (string, bool, error) {
|
||||
keydir, err := conf.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
|
||||
}
|
||||
125
vendor/github.com/ethereum/go-ethereum/node/defaults.go
generated
vendored
Normal file
125
vendor/github.com/ethereum/go-ethereum/node/defaults.go
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
DefaultGraphQLHost = "localhost" // Default host interface for the GraphQL server
|
||||
DefaultGraphQLPort = 8547 // Default TCP port for the GraphQL server
|
||||
DefaultAuthHost = "localhost" // Default host interface for the authenticated apis
|
||||
DefaultAuthPort = 8551 // Default port for the authenticated apis
|
||||
)
|
||||
|
||||
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"},
|
||||
GraphQLVirtualHosts: []string{"localhost"},
|
||||
P2P: p2p.Config{
|
||||
ListenAddr: ":30303",
|
||||
MaxPeers: 50,
|
||||
NAT: nat.Any(),
|
||||
},
|
||||
}
|
||||
|
||||
// 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 ""
|
||||
}
|
||||
121
vendor/github.com/ethereum/go-ethereum/node/doc.go
generated
vendored
Normal file
121
vendor/github.com/ethereum/go-ethereum/node/doc.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
/*
|
||||
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
|
||||
91
vendor/github.com/ethereum/go-ethereum/node/endpoints.go
generated
vendored
Normal file
91
vendor/github.com/ethereum/go-ethereum/node/endpoints.go
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
52
vendor/github.com/ethereum/go-ethereum/node/errors.go
generated
vendored
Normal file
52
vendor/github.com/ethereum/go-ethereum/node/errors.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package node
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"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)
|
||||
}
|
||||
45
vendor/github.com/ethereum/go-ethereum/node/jwt_auth.go
generated
vendored
Normal file
45
vendor/github.com/ethereum/go-ethereum/node/jwt_auth.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
80
vendor/github.com/ethereum/go-ethereum/node/jwt_handler.go
generated
vendored
Normal file
80
vendor/github.com/ethereum/go-ethereum/node/jwt_handler.go
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
31
vendor/github.com/ethereum/go-ethereum/node/lifecycle.go
generated
vendored
Normal file
31
vendor/github.com/ethereum/go-ethereum/node/lifecycle.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
}
|
||||
782
vendor/github.com/ethereum/go-ethereum/node/node.go
generated
vendored
Normal file
782
vendor/github.com/ethereum/go-ethereum/node/node.go
generated
vendored
Normal file
@@ -0,0 +1,782 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package node
|
||||
|
||||
import (
|
||||
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/prometheus/tsdb/fileutil"
|
||||
)
|
||||
|
||||
// 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 fileutil.Releaser // 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"`)
|
||||
}
|
||||
|
||||
node := &Node{
|
||||
config: conf,
|
||||
inprocHandler: rpc.NewServer(),
|
||||
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 := getKeyStoreDir(conf)
|
||||
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.
|
||||
release, _, err := fileutil.Flock(filepath.Join(instdir, "LOCK"))
|
||||
if err != nil {
|
||||
return convertFileLockError(err)
|
||||
}
|
||||
n.dirLock = release
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Node) closeDataDir() {
|
||||
// Release instance directory lock.
|
||||
if n.dirLock != nil {
|
||||
if err := n.dirLock.Release(); err != nil {
|
||||
n.log.Error("Can't release datadir lock", "err", err)
|
||||
}
|
||||
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 {
|
||||
if err := n.startInProc(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Configure IPC.
|
||||
if n.ipc.endpoint != "" {
|
||||
if err := n.ipc.start(n.rpcAPIs); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var (
|
||||
servers []*httpServer
|
||||
openAPIs, allAPIs = n.getAPIs()
|
||||
)
|
||||
|
||||
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,
|
||||
}); 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,
|
||||
}); 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
|
||||
}
|
||||
if err := server.enableRPC(allAPIs, httpConfig{
|
||||
CorsAllowedOrigins: DefaultAuthCors,
|
||||
Vhosts: n.config.AuthVirtualHosts,
|
||||
Modules: DefaultAuthModules,
|
||||
prefix: DefaultAuthPrefix,
|
||||
jwtSecret: secret,
|
||||
}); 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,
|
||||
jwtSecret: secret,
|
||||
}); 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() error {
|
||||
for _, api := range n.rpcAPIs {
|
||||
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, error) {
|
||||
return rpc.DialInProc(n.inprocHandler), nil
|
||||
}
|
||||
|
||||
// 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.NewLevelDBDatabase(n.ResolvePath(name), cache, handles, namespace, 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.NewLevelDBDatabaseWithFreezer(n.ResolvePath(name), cache, handles, n.ResolveAncient(name, ancient), namespace, 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
|
||||
}
|
||||
562
vendor/github.com/ethereum/go-ethereum/node/rpcstack.go
generated
vendored
Normal file
562
vendor/github.com/ethereum/go-ethereum/node/rpcstack.go
generated
vendored
Normal file
@@ -0,0 +1,562 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package node
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"sort"
|
||||
"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
|
||||
jwtSecret []byte // optional JWT secret
|
||||
}
|
||||
|
||||
// wsConfig is the JSON-RPC/Websocket configuration
|
||||
type wsConfig struct {
|
||||
Origins []string
|
||||
Modules []string
|
||||
prefix string // path prefix on which to mount ws handler
|
||||
jwtSecret []byte // optional JWT secret
|
||||
}
|
||||
|
||||
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 = fmt.Sprintf("%s:%d", host, 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()
|
||||
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()
|
||||
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 {
|
||||
io.Writer
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
func (w *gzipResponseWriter) WriteHeader(status int) {
|
||||
w.Header().Del("Content-Length")
|
||||
w.ResponseWriter.WriteHeader(status)
|
||||
}
|
||||
|
||||
func (w *gzipResponseWriter) Write(b []byte) (int, error) {
|
||||
return w.Writer.Write(b)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
|
||||
gz := gzPool.Get().(*gzip.Writer)
|
||||
defer gzPool.Put(gz)
|
||||
|
||||
gz.Reset(w)
|
||||
defer gz.Close()
|
||||
|
||||
next.ServeHTTP(&gzipResponseWriter{ResponseWriter: w, Writer: gz}, 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
|
||||
}
|
||||
Reference in New Issue
Block a user