forked from cerc-io/ipld-eth-server
* Add vendor dir so builds dont require dep * Pin specific version go-eth version
This commit is contained in:
+385
@@ -0,0 +1,385 @@
|
||||
// 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"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/rcrowley/go-metrics"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// NewPrivateAdminAPI creates a new API definition for the private admin methods
|
||||
// of the node itself.
|
||||
func NewPrivateAdminAPI(node *Node) *PrivateAdminAPI {
|
||||
return &PrivateAdminAPI{node: node}
|
||||
}
|
||||
|
||||
// AddPeer requests connecting to a remote node, and also maintaining the new
|
||||
// connection at all times, even reconnecting if it is lost.
|
||||
func (api *PrivateAdminAPI) 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 := discover.ParseNode(url)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid enode: %v", err)
|
||||
}
|
||||
server.AddPeer(node)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// RemovePeer disconnects from a a remote node if the connection exists
|
||||
func (api *PrivateAdminAPI) 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 := discover.ParseNode(url)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid enode: %v", err)
|
||||
}
|
||||
server.RemovePeer(node)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// PeerEvents creates an RPC subscription which receives peer events from the
|
||||
// node's p2p.Server
|
||||
func (api *PrivateAdminAPI) 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
|
||||
}
|
||||
|
||||
// StartRPC starts the HTTP RPC API server.
|
||||
func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis *string) (bool, error) {
|
||||
api.node.lock.Lock()
|
||||
defer api.node.lock.Unlock()
|
||||
|
||||
if api.node.httpHandler != nil {
|
||||
return false, fmt.Errorf("HTTP RPC already running on %s", api.node.httpEndpoint)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
allowedOrigins := api.node.config.HTTPCors
|
||||
if cors != nil {
|
||||
allowedOrigins = nil
|
||||
for _, origin := range strings.Split(*cors, ",") {
|
||||
allowedOrigins = append(allowedOrigins, strings.TrimSpace(origin))
|
||||
}
|
||||
}
|
||||
|
||||
modules := api.node.httpWhitelist
|
||||
if apis != nil {
|
||||
modules = nil
|
||||
for _, m := range strings.Split(*apis, ",") {
|
||||
modules = append(modules, strings.TrimSpace(m))
|
||||
}
|
||||
}
|
||||
|
||||
if err := api.node.startHTTP(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, allowedOrigins); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// StopRPC terminates an already running HTTP RPC API endpoint.
|
||||
func (api *PrivateAdminAPI) StopRPC() (bool, error) {
|
||||
api.node.lock.Lock()
|
||||
defer api.node.lock.Unlock()
|
||||
|
||||
if api.node.httpHandler == nil {
|
||||
return false, fmt.Errorf("HTTP RPC not running")
|
||||
}
|
||||
api.node.stopHTTP()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// StartWS starts the websocket RPC API server.
|
||||
func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *string, apis *string) (bool, error) {
|
||||
api.node.lock.Lock()
|
||||
defer api.node.lock.Unlock()
|
||||
|
||||
if api.node.wsHandler != nil {
|
||||
return false, fmt.Errorf("WebSocket RPC already running on %s", api.node.wsEndpoint)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
origins := api.node.config.WSOrigins
|
||||
if allowedOrigins != nil {
|
||||
origins = nil
|
||||
for _, origin := range strings.Split(*allowedOrigins, ",") {
|
||||
origins = append(origins, strings.TrimSpace(origin))
|
||||
}
|
||||
}
|
||||
|
||||
modules := api.node.config.WSModules
|
||||
if apis != nil {
|
||||
modules = nil
|
||||
for _, m := range strings.Split(*apis, ",") {
|
||||
modules = append(modules, strings.TrimSpace(m))
|
||||
}
|
||||
}
|
||||
|
||||
if err := api.node.startWS(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, origins, api.node.config.WSExposeAll); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// StopRPC terminates an already running websocket RPC API endpoint.
|
||||
func (api *PrivateAdminAPI) StopWS() (bool, error) {
|
||||
api.node.lock.Lock()
|
||||
defer api.node.lock.Unlock()
|
||||
|
||||
if api.node.wsHandler == nil {
|
||||
return false, fmt.Errorf("WebSocket RPC not running")
|
||||
}
|
||||
api.node.stopWS()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// PublicAdminAPI is the collection of administrative API methods exposed over
|
||||
// both secure and unsecure RPC channels.
|
||||
type PublicAdminAPI struct {
|
||||
node *Node // Node interfaced by this API
|
||||
}
|
||||
|
||||
// NewPublicAdminAPI creates a new API definition for the public admin methods
|
||||
// of the node itself.
|
||||
func NewPublicAdminAPI(node *Node) *PublicAdminAPI {
|
||||
return &PublicAdminAPI{node: node}
|
||||
}
|
||||
|
||||
// Peers retrieves all the information we know about each individual peer at the
|
||||
// protocol granularity.
|
||||
func (api *PublicAdminAPI) 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 *PublicAdminAPI) 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 *PublicAdminAPI) Datadir() string {
|
||||
return api.node.DataDir()
|
||||
}
|
||||
|
||||
// PublicDebugAPI is the collection of debugging related API methods exposed over
|
||||
// both secure and unsecure RPC channels.
|
||||
type PublicDebugAPI struct {
|
||||
node *Node // Node interfaced by this API
|
||||
}
|
||||
|
||||
// NewPublicDebugAPI creates a new API definition for the public debug methods
|
||||
// of the node itself.
|
||||
func NewPublicDebugAPI(node *Node) *PublicDebugAPI {
|
||||
return &PublicDebugAPI{node: node}
|
||||
}
|
||||
|
||||
// Metrics retrieves all the known system metric collected by the node.
|
||||
func (api *PublicDebugAPI) Metrics(raw bool) (map[string]interface{}, error) {
|
||||
// Create a rate formatter
|
||||
units := []string{"", "K", "M", "G", "T", "E", "P"}
|
||||
round := func(value float64, prec int) string {
|
||||
unit := 0
|
||||
for value >= 1000 {
|
||||
unit, value, prec = unit+1, value/1000, 2
|
||||
}
|
||||
return fmt.Sprintf(fmt.Sprintf("%%.%df%s", prec, units[unit]), value)
|
||||
}
|
||||
format := func(total float64, rate float64) string {
|
||||
return fmt.Sprintf("%s (%s/s)", round(total, 0), round(rate, 2))
|
||||
}
|
||||
// Iterate over all the metrics, and just dump for now
|
||||
counters := make(map[string]interface{})
|
||||
metrics.DefaultRegistry.Each(func(name string, metric interface{}) {
|
||||
// Create or retrieve the counter hierarchy for this metric
|
||||
root, parts := counters, strings.Split(name, "/")
|
||||
for _, part := range parts[:len(parts)-1] {
|
||||
if _, ok := root[part]; !ok {
|
||||
root[part] = make(map[string]interface{})
|
||||
}
|
||||
root = root[part].(map[string]interface{})
|
||||
}
|
||||
name = parts[len(parts)-1]
|
||||
|
||||
// Fill the counter with the metric details, formatting if requested
|
||||
if raw {
|
||||
switch metric := metric.(type) {
|
||||
case metrics.Meter:
|
||||
root[name] = map[string]interface{}{
|
||||
"AvgRate01Min": metric.Rate1(),
|
||||
"AvgRate05Min": metric.Rate5(),
|
||||
"AvgRate15Min": metric.Rate15(),
|
||||
"MeanRate": metric.RateMean(),
|
||||
"Overall": float64(metric.Count()),
|
||||
}
|
||||
|
||||
case metrics.Timer:
|
||||
root[name] = map[string]interface{}{
|
||||
"AvgRate01Min": metric.Rate1(),
|
||||
"AvgRate05Min": metric.Rate5(),
|
||||
"AvgRate15Min": metric.Rate15(),
|
||||
"MeanRate": metric.RateMean(),
|
||||
"Overall": float64(metric.Count()),
|
||||
"Percentiles": map[string]interface{}{
|
||||
"5": metric.Percentile(0.05),
|
||||
"20": metric.Percentile(0.2),
|
||||
"50": metric.Percentile(0.5),
|
||||
"80": metric.Percentile(0.8),
|
||||
"95": metric.Percentile(0.95),
|
||||
},
|
||||
}
|
||||
|
||||
default:
|
||||
root[name] = "Unknown metric type"
|
||||
}
|
||||
} else {
|
||||
switch metric := metric.(type) {
|
||||
case metrics.Meter:
|
||||
root[name] = map[string]interface{}{
|
||||
"Avg01Min": format(metric.Rate1()*60, metric.Rate1()),
|
||||
"Avg05Min": format(metric.Rate5()*300, metric.Rate5()),
|
||||
"Avg15Min": format(metric.Rate15()*900, metric.Rate15()),
|
||||
"Overall": format(float64(metric.Count()), metric.RateMean()),
|
||||
}
|
||||
|
||||
case metrics.Timer:
|
||||
root[name] = map[string]interface{}{
|
||||
"Avg01Min": format(metric.Rate1()*60, metric.Rate1()),
|
||||
"Avg05Min": format(metric.Rate5()*300, metric.Rate5()),
|
||||
"Avg15Min": format(metric.Rate15()*900, metric.Rate15()),
|
||||
"Overall": format(float64(metric.Count()), metric.RateMean()),
|
||||
"Maximum": time.Duration(metric.Max()).String(),
|
||||
"Minimum": time.Duration(metric.Min()).String(),
|
||||
"Percentiles": map[string]interface{}{
|
||||
"5": time.Duration(metric.Percentile(0.05)).String(),
|
||||
"20": time.Duration(metric.Percentile(0.2)).String(),
|
||||
"50": time.Duration(metric.Percentile(0.5)).String(),
|
||||
"80": time.Duration(metric.Percentile(0.8)).String(),
|
||||
"95": time.Duration(metric.Percentile(0.95)).String(),
|
||||
},
|
||||
}
|
||||
|
||||
default:
|
||||
root[name] = "Unknown metric type"
|
||||
}
|
||||
}
|
||||
})
|
||||
return counters, nil
|
||||
}
|
||||
|
||||
// PublicWeb3API offers helper utils
|
||||
type PublicWeb3API struct {
|
||||
stack *Node
|
||||
}
|
||||
|
||||
// NewPublicWeb3API creates a new Web3Service instance
|
||||
func NewPublicWeb3API(stack *Node) *PublicWeb3API {
|
||||
return &PublicWeb3API{stack}
|
||||
}
|
||||
|
||||
// ClientVersion returns the node name
|
||||
func (s *PublicWeb3API) 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 *PublicWeb3API) Sha3(input hexutil.Bytes) hexutil.Bytes {
|
||||
return crypto.Keccak256(input)
|
||||
}
|
||||
+425
@@ -0,0 +1,425 @@
|
||||
// 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"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/accounts/usbwallet"
|
||||
"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/p2p/discover"
|
||||
)
|
||||
|
||||
const (
|
||||
datadirPrivateKey = "nodekey" // Path within the datadir to the node's private key
|
||||
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"`
|
||||
|
||||
// UseLightweightKDF lowers the memory and CPU requirements of the key store
|
||||
// scrypt KDF at the expense of security.
|
||||
UseLightweightKDF bool `toml:",omitempty"`
|
||||
|
||||
// NoUSB disables hardware wallet monitoring and connectivity.
|
||||
NoUSB bool `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 `toml:",omitempty"`
|
||||
|
||||
// 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 `toml:",omitempty"`
|
||||
|
||||
// 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"`
|
||||
|
||||
// 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 `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 `toml:",omitempty"`
|
||||
|
||||
// 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"`
|
||||
|
||||
// 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 `toml:",omitempty"`
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// 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}
|
||||
return config.HTTPEndpoint()
|
||||
}
|
||||
|
||||
// WSEndpoint resolves an 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()
|
||||
}
|
||||
|
||||
// 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": true,
|
||||
"trusted-nodes.json": true,
|
||||
}
|
||||
|
||||
// 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 c.name() == "geth" && isOldGethResource[path] {
|
||||
oldpath := ""
|
||||
if c.Name == "geth" {
|
||||
oldpath = filepath.Join(c.DataDir, path)
|
||||
}
|
||||
if oldpath != "" && common.FileExist(oldpath) {
|
||||
// TODO: print warning
|
||||
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
|
||||
}
|
||||
|
||||
// StaticNodes returns a list of node enode URLs configured as static nodes.
|
||||
func (c *Config) StaticNodes() []*discover.Node {
|
||||
return c.parsePersistentNodes(c.resolvePath(datadirStaticNodes))
|
||||
}
|
||||
|
||||
// TrustedNodes returns a list of node enode URLs configured as trusted nodes.
|
||||
func (c *Config) TrustedNodes() []*discover.Node {
|
||||
return c.parsePersistentNodes(c.resolvePath(datadirTrustedNodes))
|
||||
}
|
||||
|
||||
// parsePersistentNodes parses a list of discovery node URLs loaded from a .json
|
||||
// file from within the data directory.
|
||||
func (c *Config) parsePersistentNodes(path string) []*discover.Node {
|
||||
// Short circuit if no node config is present
|
||||
if c.DataDir == "" {
|
||||
return nil
|
||||
}
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
return nil
|
||||
}
|
||||
// Load the nodes from the config file.
|
||||
var nodelist []string
|
||||
if err := common.LoadJSON(path, &nodelist); err != nil {
|
||||
log.Error(fmt.Sprintf("Can't load node file %s: %v", path, err))
|
||||
return nil
|
||||
}
|
||||
// Interpret the list as a discovery node array
|
||||
var nodes []*discover.Node
|
||||
for _, url := range nodelist {
|
||||
if url == "" {
|
||||
continue
|
||||
}
|
||||
node, err := discover.ParseNode(url)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Node URL %s: %v\n", url, err))
|
||||
continue
|
||||
}
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
// AccountConfig determines the settings for scrypt and keydirectory
|
||||
func (c *Config) AccountConfig() (int, int, string, error) {
|
||||
scryptN := keystore.StandardScryptN
|
||||
scryptP := keystore.StandardScryptP
|
||||
if c.UseLightweightKDF {
|
||||
scryptN = keystore.LightScryptN
|
||||
scryptP = keystore.LightScryptP
|
||||
}
|
||||
|
||||
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 scryptN, scryptP, keydir, err
|
||||
}
|
||||
|
||||
func makeAccountManager(conf *Config) (*accounts.Manager, string, error) {
|
||||
scryptN, scryptP, keydir, err := conf.AccountConfig()
|
||||
var ephemeral string
|
||||
if keydir == "" {
|
||||
// There is no datadir.
|
||||
keydir, err = ioutil.TempDir("", "go-ethereum-keystore")
|
||||
ephemeral = keydir
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if err := os.MkdirAll(keydir, 0700); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
// Assemble the account manager and supported backends
|
||||
backends := []accounts.Backend{
|
||||
keystore.NewKeyStore(keydir, scryptN, scryptP),
|
||||
}
|
||||
if !conf.NoUSB {
|
||||
// Start a USB hub for Ledger hardware wallets
|
||||
if ledgerhub, err := usbwallet.NewLedgerHub(); err != nil {
|
||||
log.Warn(fmt.Sprintf("Failed to start Ledger hub, disabling: %v", err))
|
||||
} else {
|
||||
backends = append(backends, ledgerhub)
|
||||
}
|
||||
// Start a USB hub for Trezor hardware wallets
|
||||
if trezorhub, err := usbwallet.NewTrezorHub(); err != nil {
|
||||
log.Warn(fmt.Sprintf("Failed to start Trezor hub, disabling: %v", err))
|
||||
} else {
|
||||
backends = append(backends, trezorhub)
|
||||
}
|
||||
}
|
||||
return accounts.NewManager(backends...), ephemeral, nil
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
)
|
||||
|
||||
// Tests that datadirs can be successfully created, be them manually configured
|
||||
// ones or automatically generated temporary ones.
|
||||
func TestDatadirCreation(t *testing.T) {
|
||||
// Create a temporary data dir and check that it can be used by a node
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create manual data dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
if _, err := New(&Config{DataDir: dir}); err != nil {
|
||||
t.Fatalf("failed to create stack with existing datadir: %v", err)
|
||||
}
|
||||
// Generate a long non-existing datadir path and check that it gets created by a node
|
||||
dir = filepath.Join(dir, "a", "b", "c", "d", "e", "f")
|
||||
if _, err := New(&Config{DataDir: dir}); err != nil {
|
||||
t.Fatalf("failed to create stack with creatable datadir: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(dir); err != nil {
|
||||
t.Fatalf("freshly created datadir not accessible: %v", err)
|
||||
}
|
||||
// Verify that an impossible datadir fails creation
|
||||
file, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temporary file: %v", err)
|
||||
}
|
||||
defer os.Remove(file.Name())
|
||||
|
||||
dir = filepath.Join(file.Name(), "invalid/path")
|
||||
if _, err := New(&Config{DataDir: dir}); err == nil {
|
||||
t.Fatalf("protocol stack created with an invalid datadir")
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that IPC paths are correctly resolved to valid endpoints of different
|
||||
// platforms.
|
||||
func TestIPCPathResolution(t *testing.T) {
|
||||
var tests = []struct {
|
||||
DataDir string
|
||||
IPCPath string
|
||||
Windows bool
|
||||
Endpoint string
|
||||
}{
|
||||
{"", "", false, ""},
|
||||
{"data", "", false, ""},
|
||||
{"", "geth.ipc", false, filepath.Join(os.TempDir(), "geth.ipc")},
|
||||
{"data", "geth.ipc", false, "data/geth.ipc"},
|
||||
{"data", "./geth.ipc", false, "./geth.ipc"},
|
||||
{"data", "/geth.ipc", false, "/geth.ipc"},
|
||||
{"", "", true, ``},
|
||||
{"data", "", true, ``},
|
||||
{"", "geth.ipc", true, `\\.\pipe\geth.ipc`},
|
||||
{"data", "geth.ipc", true, `\\.\pipe\geth.ipc`},
|
||||
{"data", `\\.\pipe\geth.ipc`, true, `\\.\pipe\geth.ipc`},
|
||||
}
|
||||
for i, test := range tests {
|
||||
// Only run when platform/test match
|
||||
if (runtime.GOOS == "windows") == test.Windows {
|
||||
if endpoint := (&Config{DataDir: test.DataDir, IPCPath: test.IPCPath}).IPCEndpoint(); endpoint != test.Endpoint {
|
||||
t.Errorf("test %d: IPC endpoint mismatch: have %s, want %s", i, endpoint, test.Endpoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that node keys can be correctly created, persisted, loaded and/or made
|
||||
// ephemeral.
|
||||
func TestNodeKeyPersistency(t *testing.T) {
|
||||
// Create a temporary folder and make sure no key is present
|
||||
dir, err := ioutil.TempDir("", "node-test")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temporary data directory: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
keyfile := filepath.Join(dir, "unit-test", datadirPrivateKey)
|
||||
|
||||
// Configure a node with a preset key and ensure it's not persisted
|
||||
key, err := crypto.GenerateKey()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate one-shot node key: %v", err)
|
||||
}
|
||||
config := &Config{Name: "unit-test", DataDir: dir, P2P: p2p.Config{PrivateKey: key}}
|
||||
config.NodeKey()
|
||||
if _, err := os.Stat(filepath.Join(keyfile)); err == nil {
|
||||
t.Fatalf("one-shot node key persisted to data directory")
|
||||
}
|
||||
|
||||
// Configure a node with no preset key and ensure it is persisted this time
|
||||
config = &Config{Name: "unit-test", DataDir: dir}
|
||||
config.NodeKey()
|
||||
if _, err := os.Stat(keyfile); err != nil {
|
||||
t.Fatalf("node key not persisted to data directory: %v", err)
|
||||
}
|
||||
if _, err = crypto.LoadECDSA(keyfile); err != nil {
|
||||
t.Fatalf("failed to load freshly persisted node key: %v", err)
|
||||
}
|
||||
blob1, err := ioutil.ReadFile(keyfile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read freshly persisted node key: %v", err)
|
||||
}
|
||||
|
||||
// Configure a new node and ensure the previously persisted key is loaded
|
||||
config = &Config{Name: "unit-test", DataDir: dir}
|
||||
config.NodeKey()
|
||||
blob2, err := ioutil.ReadFile(filepath.Join(keyfile))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read previously persisted node key: %v", err)
|
||||
}
|
||||
if !bytes.Equal(blob1, blob2) {
|
||||
t.Fatalf("persisted node key mismatch: have %x, want %x", blob2, blob1)
|
||||
}
|
||||
|
||||
// Configure ephemeral node and ensure no key is dumped locally
|
||||
config = &Config{Name: "unit-test", DataDir: ""}
|
||||
config.NodeKey()
|
||||
if _, err := os.Stat(filepath.Join(".", "unit-test", datadirPrivateKey)); err == nil {
|
||||
t.Fatalf("ephemeral node key persisted to disk")
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
// DefaultConfig contains reasonable default settings.
|
||||
var DefaultConfig = Config{
|
||||
DataDir: DefaultDataDir(),
|
||||
HTTPPort: DefaultHTTPPort,
|
||||
HTTPModules: []string{"net", "web3"},
|
||||
WSPort: DefaultWSPort,
|
||||
WSModules: []string{"net", "web3"},
|
||||
P2P: p2p.Config{
|
||||
ListenAddr: ":30303",
|
||||
DiscoveryV5Addr: ":30304",
|
||||
MaxPeers: 25,
|
||||
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 != "" {
|
||||
if runtime.GOOS == "darwin" {
|
||||
return filepath.Join(home, "Library", "Ethereum")
|
||||
} else if runtime.GOOS == "windows" {
|
||||
return filepath.Join(home, "AppData", "Roaming", "Ethereum")
|
||||
} else {
|
||||
return filepath.Join(home, ".ethereum")
|
||||
}
|
||||
}
|
||||
// As we cannot guess a stable location, return empty and handle later
|
||||
return ""
|
||||
}
|
||||
|
||||
func homeDir() string {
|
||||
if home := os.Getenv("HOME"); home != "" {
|
||||
return home
|
||||
}
|
||||
if usr, err := user.Current(); err == nil {
|
||||
return usr.HomeDir
|
||||
}
|
||||
return ""
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
// 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.
|
||||
|
||||
|
||||
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, openening 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. Mode 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 A
|
||||
keystore/ -- account key store, used by both instances
|
||||
*/
|
||||
package node
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
// 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
|
||||
}
|
||||
|
||||
// DuplicateServiceError is returned during Node startup if a registered service
|
||||
// constructor returns a service of the same type that was already started.
|
||||
type DuplicateServiceError struct {
|
||||
Kind reflect.Type
|
||||
}
|
||||
|
||||
// Error generates a textual representation of the duplicate service error.
|
||||
func (e *DuplicateServiceError) Error() string {
|
||||
return fmt.Sprintf("duplicate service: %v", e.Kind)
|
||||
}
|
||||
|
||||
// StopError is returned if a Node fails to stop either any of its registered
|
||||
// services or itself.
|
||||
type StopError struct {
|
||||
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)
|
||||
}
|
||||
+678
@@ -0,0 +1,678 @@
|
||||
// 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"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/internal/debug"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/prometheus/prometheus/util/flock"
|
||||
)
|
||||
|
||||
// Node is a container on which services can be registered.
|
||||
type Node struct {
|
||||
eventmux *event.TypeMux // Event multiplexer used between the services of a stack
|
||||
config *Config
|
||||
accman *accounts.Manager
|
||||
|
||||
ephemeralKeystore string // if non-empty, the key directory that will be removed by Stop
|
||||
instanceDirLock flock.Releaser // prevents concurrent use of instance directory
|
||||
|
||||
serverConfig p2p.Config
|
||||
server *p2p.Server // Currently running P2P networking layer
|
||||
|
||||
serviceFuncs []ServiceConstructor // Service constructors (in dependency order)
|
||||
services map[reflect.Type]Service // Currently running services
|
||||
|
||||
rpcAPIs []rpc.API // List of APIs currently provided by the node
|
||||
inprocHandler *rpc.Server // In-process RPC request handler to process the API requests
|
||||
|
||||
ipcEndpoint string // IPC endpoint to listen at (empty = IPC disabled)
|
||||
ipcListener net.Listener // IPC RPC listener socket to serve API requests
|
||||
ipcHandler *rpc.Server // IPC RPC request handler to process the API requests
|
||||
|
||||
httpEndpoint string // HTTP endpoint (interface + port) to listen at (empty = HTTP disabled)
|
||||
httpWhitelist []string // HTTP RPC modules to allow through this endpoint
|
||||
httpListener net.Listener // HTTP RPC listener socket to server API requests
|
||||
httpHandler *rpc.Server // HTTP RPC request handler to process the API requests
|
||||
|
||||
wsEndpoint string // Websocket endpoint (interface + port) to listen at (empty = websocket disabled)
|
||||
wsListener net.Listener // Websocket RPC listener socket to server API requests
|
||||
wsHandler *rpc.Server // Websocket RPC request handler to process the API requests
|
||||
|
||||
stop chan struct{} // Channel to wait for termination notifications
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
// 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"`)
|
||||
}
|
||||
// Ensure that the AccountManager method works before the node has started.
|
||||
// We rely on this in cmd/geth.
|
||||
am, ephemeralKeystore, err := makeAccountManager(conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Note: any interaction with Config that would create/touch files
|
||||
// in the data directory or instance directory is delayed until Start.
|
||||
return &Node{
|
||||
accman: am,
|
||||
ephemeralKeystore: ephemeralKeystore,
|
||||
config: conf,
|
||||
serviceFuncs: []ServiceConstructor{},
|
||||
ipcEndpoint: conf.IPCEndpoint(),
|
||||
httpEndpoint: conf.HTTPEndpoint(),
|
||||
wsEndpoint: conf.WSEndpoint(),
|
||||
eventmux: new(event.TypeMux),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Register injects a new service into the node's stack. The service created by
|
||||
// the passed constructor must be unique in its type with regard to sibling ones.
|
||||
func (n *Node) Register(constructor ServiceConstructor) error {
|
||||
n.lock.Lock()
|
||||
defer n.lock.Unlock()
|
||||
|
||||
if n.server != nil {
|
||||
return ErrNodeRunning
|
||||
}
|
||||
n.serviceFuncs = append(n.serviceFuncs, constructor)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start create a live P2P node and starts running it.
|
||||
func (n *Node) Start() error {
|
||||
n.lock.Lock()
|
||||
defer n.lock.Unlock()
|
||||
|
||||
// Short circuit if the node's already running
|
||||
if n.server != nil {
|
||||
return ErrNodeRunning
|
||||
}
|
||||
if err := n.openDataDir(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize the p2p server. This creates the node key and
|
||||
// discovery databases.
|
||||
n.serverConfig = n.config.P2P
|
||||
n.serverConfig.PrivateKey = n.config.NodeKey()
|
||||
n.serverConfig.Name = n.config.NodeName()
|
||||
if n.serverConfig.StaticNodes == nil {
|
||||
n.serverConfig.StaticNodes = n.config.StaticNodes()
|
||||
}
|
||||
if n.serverConfig.TrustedNodes == nil {
|
||||
n.serverConfig.TrustedNodes = n.config.TrustedNodes()
|
||||
}
|
||||
if n.serverConfig.NodeDatabase == "" {
|
||||
n.serverConfig.NodeDatabase = n.config.NodeDB()
|
||||
}
|
||||
running := &p2p.Server{Config: n.serverConfig}
|
||||
log.Info("Starting peer-to-peer node", "instance", n.serverConfig.Name)
|
||||
|
||||
// Otherwise copy and specialize the P2P configuration
|
||||
services := make(map[reflect.Type]Service)
|
||||
for _, constructor := range n.serviceFuncs {
|
||||
// Create a new context for the particular service
|
||||
ctx := &ServiceContext{
|
||||
config: n.config,
|
||||
services: make(map[reflect.Type]Service),
|
||||
EventMux: n.eventmux,
|
||||
AccountManager: n.accman,
|
||||
}
|
||||
for kind, s := range services { // copy needed for threaded access
|
||||
ctx.services[kind] = s
|
||||
}
|
||||
// Construct and save the service
|
||||
service, err := constructor(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kind := reflect.TypeOf(service)
|
||||
if _, exists := services[kind]; exists {
|
||||
return &DuplicateServiceError{Kind: kind}
|
||||
}
|
||||
services[kind] = service
|
||||
}
|
||||
// Gather the protocols and start the freshly assembled P2P server
|
||||
for _, service := range services {
|
||||
running.Protocols = append(running.Protocols, service.Protocols()...)
|
||||
}
|
||||
if err := running.Start(); err != nil {
|
||||
return convertFileLockError(err)
|
||||
}
|
||||
// Start each of the services
|
||||
started := []reflect.Type{}
|
||||
for kind, service := range services {
|
||||
// Start the next service, stopping all previous upon failure
|
||||
if err := service.Start(running); err != nil {
|
||||
for _, kind := range started {
|
||||
services[kind].Stop()
|
||||
}
|
||||
running.Stop()
|
||||
|
||||
return err
|
||||
}
|
||||
// Mark the service started for potential cleanup
|
||||
started = append(started, kind)
|
||||
}
|
||||
// Lastly start the configured RPC interfaces
|
||||
if err := n.startRPC(services); err != nil {
|
||||
for _, service := range services {
|
||||
service.Stop()
|
||||
}
|
||||
running.Stop()
|
||||
return err
|
||||
}
|
||||
// Finish initializing the startup
|
||||
n.services = services
|
||||
n.server = running
|
||||
n.stop = make(chan struct{})
|
||||
|
||||
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 := flock.New(filepath.Join(instdir, "LOCK"))
|
||||
if err != nil {
|
||||
return convertFileLockError(err)
|
||||
}
|
||||
n.instanceDirLock = release
|
||||
return nil
|
||||
}
|
||||
|
||||
// startRPC is a helper method to start all the various RPC endpoint 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(services map[reflect.Type]Service) error {
|
||||
// Gather all the possible APIs to surface
|
||||
apis := n.apis()
|
||||
for _, service := range services {
|
||||
apis = append(apis, service.APIs()...)
|
||||
}
|
||||
// Start the various API endpoints, terminating all in case of errors
|
||||
if err := n.startInProc(apis); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := n.startIPC(apis); err != nil {
|
||||
n.stopInProc()
|
||||
return err
|
||||
}
|
||||
if err := n.startHTTP(n.httpEndpoint, apis, n.config.HTTPModules, n.config.HTTPCors); err != nil {
|
||||
n.stopIPC()
|
||||
n.stopInProc()
|
||||
return err
|
||||
}
|
||||
if err := n.startWS(n.wsEndpoint, apis, n.config.WSModules, n.config.WSOrigins, n.config.WSExposeAll); err != nil {
|
||||
n.stopHTTP()
|
||||
n.stopIPC()
|
||||
n.stopInProc()
|
||||
return err
|
||||
}
|
||||
// All API endpoints started successfully
|
||||
n.rpcAPIs = apis
|
||||
return nil
|
||||
}
|
||||
|
||||
// startInProc initializes an in-process RPC endpoint.
|
||||
func (n *Node) startInProc(apis []rpc.API) error {
|
||||
// Register all the APIs exposed by the services
|
||||
handler := rpc.NewServer()
|
||||
for _, api := range apis {
|
||||
if err := handler.RegisterName(api.Namespace, api.Service); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debug(fmt.Sprintf("InProc registered %T under '%s'", api.Service, api.Namespace))
|
||||
}
|
||||
n.inprocHandler = handler
|
||||
return nil
|
||||
}
|
||||
|
||||
// stopInProc terminates the in-process RPC endpoint.
|
||||
func (n *Node) stopInProc() {
|
||||
if n.inprocHandler != nil {
|
||||
n.inprocHandler.Stop()
|
||||
n.inprocHandler = nil
|
||||
}
|
||||
}
|
||||
|
||||
// startIPC initializes and starts the IPC RPC endpoint.
|
||||
func (n *Node) startIPC(apis []rpc.API) error {
|
||||
// Short circuit if the IPC endpoint isn't being exposed
|
||||
if n.ipcEndpoint == "" {
|
||||
return nil
|
||||
}
|
||||
// Register all the APIs exposed by the services
|
||||
handler := rpc.NewServer()
|
||||
for _, api := range apis {
|
||||
if err := handler.RegisterName(api.Namespace, api.Service); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debug(fmt.Sprintf("IPC registered %T under '%s'", api.Service, api.Namespace))
|
||||
}
|
||||
// All APIs registered, start the IPC listener
|
||||
var (
|
||||
listener net.Listener
|
||||
err error
|
||||
)
|
||||
if listener, err = rpc.CreateIPCListener(n.ipcEndpoint); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
log.Info(fmt.Sprintf("IPC endpoint opened: %s", n.ipcEndpoint))
|
||||
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
// Terminate if the listener was closed
|
||||
n.lock.RLock()
|
||||
closed := n.ipcListener == nil
|
||||
n.lock.RUnlock()
|
||||
if closed {
|
||||
return
|
||||
}
|
||||
// Not closed, just some error; report and continue
|
||||
log.Error(fmt.Sprintf("IPC accept failed: %v", err))
|
||||
continue
|
||||
}
|
||||
go handler.ServeCodec(rpc.NewJSONCodec(conn), rpc.OptionMethodInvocation|rpc.OptionSubscriptions)
|
||||
}
|
||||
}()
|
||||
// All listeners booted successfully
|
||||
n.ipcListener = listener
|
||||
n.ipcHandler = handler
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// stopIPC terminates the IPC RPC endpoint.
|
||||
func (n *Node) stopIPC() {
|
||||
if n.ipcListener != nil {
|
||||
n.ipcListener.Close()
|
||||
n.ipcListener = nil
|
||||
|
||||
log.Info(fmt.Sprintf("IPC endpoint closed: %s", n.ipcEndpoint))
|
||||
}
|
||||
if n.ipcHandler != nil {
|
||||
n.ipcHandler.Stop()
|
||||
n.ipcHandler = nil
|
||||
}
|
||||
}
|
||||
|
||||
// startHTTP initializes and starts the HTTP RPC endpoint.
|
||||
func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors []string) error {
|
||||
// Short circuit if the HTTP endpoint isn't being exposed
|
||||
if endpoint == "" {
|
||||
return nil
|
||||
}
|
||||
// Generate the whitelist based on the allowed modules
|
||||
whitelist := make(map[string]bool)
|
||||
for _, module := range modules {
|
||||
whitelist[module] = true
|
||||
}
|
||||
// Register all the APIs exposed by the services
|
||||
handler := rpc.NewServer()
|
||||
for _, api := range apis {
|
||||
if whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) {
|
||||
if err := handler.RegisterName(api.Namespace, api.Service); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debug(fmt.Sprintf("HTTP registered %T under '%s'", api.Service, api.Namespace))
|
||||
}
|
||||
}
|
||||
// All APIs registered, start the HTTP listener
|
||||
var (
|
||||
listener net.Listener
|
||||
err error
|
||||
)
|
||||
if listener, err = net.Listen("tcp", endpoint); err != nil {
|
||||
return err
|
||||
}
|
||||
go rpc.NewHTTPServer(cors, handler).Serve(listener)
|
||||
log.Info(fmt.Sprintf("HTTP endpoint opened: http://%s", endpoint))
|
||||
|
||||
// All listeners booted successfully
|
||||
n.httpEndpoint = endpoint
|
||||
n.httpListener = listener
|
||||
n.httpHandler = handler
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// stopHTTP terminates the HTTP RPC endpoint.
|
||||
func (n *Node) stopHTTP() {
|
||||
if n.httpListener != nil {
|
||||
n.httpListener.Close()
|
||||
n.httpListener = nil
|
||||
|
||||
log.Info(fmt.Sprintf("HTTP endpoint closed: http://%s", n.httpEndpoint))
|
||||
}
|
||||
if n.httpHandler != nil {
|
||||
n.httpHandler.Stop()
|
||||
n.httpHandler = nil
|
||||
}
|
||||
}
|
||||
|
||||
// startWS initializes and starts the websocket RPC endpoint.
|
||||
func (n *Node) startWS(endpoint string, apis []rpc.API, modules []string, wsOrigins []string, exposeAll bool) error {
|
||||
// Short circuit if the WS endpoint isn't being exposed
|
||||
if endpoint == "" {
|
||||
return nil
|
||||
}
|
||||
// Generate the whitelist based on the allowed modules
|
||||
whitelist := make(map[string]bool)
|
||||
for _, module := range modules {
|
||||
whitelist[module] = true
|
||||
}
|
||||
// Register all the APIs exposed by the services
|
||||
handler := rpc.NewServer()
|
||||
for _, api := range apis {
|
||||
if exposeAll || whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) {
|
||||
if err := handler.RegisterName(api.Namespace, api.Service); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debug(fmt.Sprintf("WebSocket registered %T under '%s'", api.Service, api.Namespace))
|
||||
}
|
||||
}
|
||||
// All APIs registered, start the HTTP listener
|
||||
var (
|
||||
listener net.Listener
|
||||
err error
|
||||
)
|
||||
if listener, err = net.Listen("tcp", endpoint); err != nil {
|
||||
return err
|
||||
}
|
||||
go rpc.NewWSServer(wsOrigins, handler).Serve(listener)
|
||||
log.Info(fmt.Sprintf("WebSocket endpoint opened: ws://%s", listener.Addr()))
|
||||
|
||||
// All listeners booted successfully
|
||||
n.wsEndpoint = endpoint
|
||||
n.wsListener = listener
|
||||
n.wsHandler = handler
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// stopWS terminates the websocket RPC endpoint.
|
||||
func (n *Node) stopWS() {
|
||||
if n.wsListener != nil {
|
||||
n.wsListener.Close()
|
||||
n.wsListener = nil
|
||||
|
||||
log.Info(fmt.Sprintf("WebSocket endpoint closed: ws://%s", n.wsEndpoint))
|
||||
}
|
||||
if n.wsHandler != nil {
|
||||
n.wsHandler.Stop()
|
||||
n.wsHandler = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Stop terminates a running node along with all it's services. In the node was
|
||||
// not started, an error is returned.
|
||||
func (n *Node) Stop() error {
|
||||
n.lock.Lock()
|
||||
defer n.lock.Unlock()
|
||||
|
||||
// Short circuit if the node's not running
|
||||
if n.server == nil {
|
||||
return ErrNodeStopped
|
||||
}
|
||||
|
||||
// Terminate the API, services and the p2p server.
|
||||
n.stopWS()
|
||||
n.stopHTTP()
|
||||
n.stopIPC()
|
||||
n.rpcAPIs = nil
|
||||
failure := &StopError{
|
||||
Services: make(map[reflect.Type]error),
|
||||
}
|
||||
for kind, service := range n.services {
|
||||
if err := service.Stop(); err != nil {
|
||||
failure.Services[kind] = err
|
||||
}
|
||||
}
|
||||
n.server.Stop()
|
||||
n.services = nil
|
||||
n.server = nil
|
||||
|
||||
// Release instance directory lock.
|
||||
if n.instanceDirLock != nil {
|
||||
if err := n.instanceDirLock.Release(); err != nil {
|
||||
log.Error("Can't release datadir lock", "err", err)
|
||||
}
|
||||
n.instanceDirLock = nil
|
||||
}
|
||||
|
||||
// unblock n.Wait
|
||||
close(n.stop)
|
||||
|
||||
// Remove the keystore if it was created ephemerally.
|
||||
var keystoreErr error
|
||||
if n.ephemeralKeystore != "" {
|
||||
keystoreErr = os.RemoveAll(n.ephemeralKeystore)
|
||||
}
|
||||
|
||||
if len(failure.Services) > 0 {
|
||||
return failure
|
||||
}
|
||||
if keystoreErr != nil {
|
||||
return keystoreErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait blocks the thread until the node is stopped. If the node is not running
|
||||
// at the time of invocation, the method immediately returns.
|
||||
func (n *Node) Wait() {
|
||||
n.lock.RLock()
|
||||
if n.server == nil {
|
||||
n.lock.RUnlock()
|
||||
return
|
||||
}
|
||||
stop := n.stop
|
||||
n.lock.RUnlock()
|
||||
|
||||
<-stop
|
||||
}
|
||||
|
||||
// Restart terminates a running node and boots up a new one in its place. If the
|
||||
// node isn't running, an error is returned.
|
||||
func (n *Node) Restart() error {
|
||||
if err := n.Stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := n.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Attach creates an RPC client attached to an in-process API handler.
|
||||
func (n *Node) Attach() (*rpc.Client, error) {
|
||||
n.lock.RLock()
|
||||
defer n.lock.RUnlock()
|
||||
|
||||
if n.server == nil {
|
||||
return nil, ErrNodeStopped
|
||||
}
|
||||
return rpc.DialInProc(n.inprocHandler), nil
|
||||
}
|
||||
|
||||
// RPCHandler returns the in-process RPC request handler.
|
||||
func (n *Node) RPCHandler() (*rpc.Server, error) {
|
||||
n.lock.RLock()
|
||||
defer n.lock.RUnlock()
|
||||
|
||||
if n.inprocHandler == nil {
|
||||
return nil, ErrNodeStopped
|
||||
}
|
||||
return n.inprocHandler, nil
|
||||
}
|
||||
|
||||
// Server retrieves the currently running P2P network layer. This method is meant
|
||||
// only to inspect fields of the currently running server, life cycle management
|
||||
// should be left to this Node entity.
|
||||
func (n *Node) Server() *p2p.Server {
|
||||
n.lock.RLock()
|
||||
defer n.lock.RUnlock()
|
||||
|
||||
return n.server
|
||||
}
|
||||
|
||||
// Service retrieves a currently running service registered of a specific type.
|
||||
func (n *Node) Service(service interface{}) error {
|
||||
n.lock.RLock()
|
||||
defer n.lock.RUnlock()
|
||||
|
||||
// Short circuit if the node's not running
|
||||
if n.server == nil {
|
||||
return ErrNodeStopped
|
||||
}
|
||||
// Otherwise try to find the service to return
|
||||
element := reflect.ValueOf(service).Elem()
|
||||
if running, ok := n.services[element.Type()]; ok {
|
||||
element.Set(reflect.ValueOf(running))
|
||||
return nil
|
||||
}
|
||||
return ErrServiceUnknown
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
// 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.ipcEndpoint
|
||||
}
|
||||
|
||||
// HTTPEndpoint retrieves the current HTTP endpoint used by the protocol stack.
|
||||
func (n *Node) HTTPEndpoint() string {
|
||||
return n.httpEndpoint
|
||||
}
|
||||
|
||||
// WSEndpoint retrieves the current WS endpoint used by the protocol stack.
|
||||
func (n *Node) WSEndpoint() string {
|
||||
return n.wsEndpoint
|
||||
}
|
||||
|
||||
// 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) (ethdb.Database, error) {
|
||||
if n.config.DataDir == "" {
|
||||
return ethdb.NewMemDatabase()
|
||||
}
|
||||
return ethdb.NewLDBDatabase(n.config.resolvePath(name), cache, handles)
|
||||
}
|
||||
|
||||
// ResolvePath returns the absolute path of a resource in the instance directory.
|
||||
func (n *Node) ResolvePath(x string) string {
|
||||
return n.config.resolvePath(x)
|
||||
}
|
||||
|
||||
// apis returns the collection of RPC descriptors this node offers.
|
||||
func (n *Node) apis() []rpc.API {
|
||||
return []rpc.API{
|
||||
{
|
||||
Namespace: "admin",
|
||||
Version: "1.0",
|
||||
Service: NewPrivateAdminAPI(n),
|
||||
}, {
|
||||
Namespace: "admin",
|
||||
Version: "1.0",
|
||||
Service: NewPublicAdminAPI(n),
|
||||
Public: true,
|
||||
}, {
|
||||
Namespace: "debug",
|
||||
Version: "1.0",
|
||||
Service: debug.Handler,
|
||||
}, {
|
||||
Namespace: "debug",
|
||||
Version: "1.0",
|
||||
Service: NewPublicDebugAPI(n),
|
||||
Public: true,
|
||||
}, {
|
||||
Namespace: "web3",
|
||||
Version: "1.0",
|
||||
Service: NewPublicWeb3API(n),
|
||||
Public: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
// 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_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// SampleService is a trivial network service that can be attached to a node for
|
||||
// life cycle management.
|
||||
//
|
||||
// The following methods are needed to implement a node.Service:
|
||||
// - Protocols() []p2p.Protocol - devp2p protocols the service can communicate on
|
||||
// - APIs() []rpc.API - api methods the service wants to expose on rpc channels
|
||||
// - Start() error - method invoked when the node is ready to start the service
|
||||
// - Stop() error - method invoked when the node terminates the service
|
||||
type SampleService struct{}
|
||||
|
||||
func (s *SampleService) Protocols() []p2p.Protocol { return nil }
|
||||
func (s *SampleService) APIs() []rpc.API { return nil }
|
||||
func (s *SampleService) Start(*p2p.Server) error { fmt.Println("Service starting..."); return nil }
|
||||
func (s *SampleService) Stop() error { fmt.Println("Service stopping..."); return nil }
|
||||
|
||||
func ExampleService() {
|
||||
// Create a network node to run protocols with the default values.
|
||||
stack, err := node.New(&node.Config{})
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create network node: %v", err)
|
||||
}
|
||||
// Create and register a simple network service. This is done through the definition
|
||||
// of a node.ServiceConstructor that will instantiate a node.Service. The reason for
|
||||
// the factory method approach is to support service restarts without relying on the
|
||||
// individual implementations' support for such operations.
|
||||
constructor := func(context *node.ServiceContext) (node.Service, error) {
|
||||
return new(SampleService), nil
|
||||
}
|
||||
if err := stack.Register(constructor); err != nil {
|
||||
log.Fatalf("Failed to register service: %v", err)
|
||||
}
|
||||
// Boot up the entire protocol stack, do a restart and terminate
|
||||
if err := stack.Start(); err != nil {
|
||||
log.Fatalf("Failed to start the protocol stack: %v", err)
|
||||
}
|
||||
if err := stack.Restart(); err != nil {
|
||||
log.Fatalf("Failed to restart the protocol stack: %v", err)
|
||||
}
|
||||
if err := stack.Stop(); err != nil {
|
||||
log.Fatalf("Failed to stop the protocol stack: %v", err)
|
||||
}
|
||||
// Output:
|
||||
// Service starting...
|
||||
// Service stopping...
|
||||
// Service starting...
|
||||
// Service stopping...
|
||||
}
|
||||
+575
@@ -0,0 +1,575 @@
|
||||
// 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"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
var (
|
||||
testNodeKey, _ = crypto.GenerateKey()
|
||||
)
|
||||
|
||||
func testNodeConfig() *Config {
|
||||
return &Config{
|
||||
Name: "test node",
|
||||
P2P: p2p.Config{PrivateKey: testNodeKey},
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that an empty protocol stack can be started, restarted and stopped.
|
||||
func TestNodeLifeCycle(t *testing.T) {
|
||||
stack, err := New(testNodeConfig())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create protocol stack: %v", err)
|
||||
}
|
||||
// Ensure that a stopped node can be stopped again
|
||||
for i := 0; i < 3; i++ {
|
||||
if err := stack.Stop(); err != ErrNodeStopped {
|
||||
t.Fatalf("iter %d: stop failure mismatch: have %v, want %v", i, err, ErrNodeStopped)
|
||||
}
|
||||
}
|
||||
// Ensure that a node can be successfully started, but only once
|
||||
if err := stack.Start(); err != nil {
|
||||
t.Fatalf("failed to start node: %v", err)
|
||||
}
|
||||
if err := stack.Start(); err != ErrNodeRunning {
|
||||
t.Fatalf("start failure mismatch: have %v, want %v ", err, ErrNodeRunning)
|
||||
}
|
||||
// Ensure that a node can be restarted arbitrarily many times
|
||||
for i := 0; i < 3; i++ {
|
||||
if err := stack.Restart(); err != nil {
|
||||
t.Fatalf("iter %d: failed to restart node: %v", i, err)
|
||||
}
|
||||
}
|
||||
// Ensure that a node can be stopped, but only once
|
||||
if err := stack.Stop(); err != nil {
|
||||
t.Fatalf("failed to stop node: %v", err)
|
||||
}
|
||||
if err := stack.Stop(); err != ErrNodeStopped {
|
||||
t.Fatalf("stop failure mismatch: have %v, want %v ", err, ErrNodeStopped)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that if the data dir is already in use, an appropriate error is returned.
|
||||
func TestNodeUsedDataDir(t *testing.T) {
|
||||
// Create a temporary folder to use as the data directory
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temporary data directory: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// Create a new node based on the data directory
|
||||
original, err := New(&Config{DataDir: dir})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create original protocol stack: %v", err)
|
||||
}
|
||||
if err := original.Start(); err != nil {
|
||||
t.Fatalf("failed to start original protocol stack: %v", err)
|
||||
}
|
||||
defer original.Stop()
|
||||
|
||||
// Create a second node based on the same data directory and ensure failure
|
||||
duplicate, err := New(&Config{DataDir: dir})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create duplicate protocol stack: %v", err)
|
||||
}
|
||||
if err := duplicate.Start(); err != ErrDatadirUsed {
|
||||
t.Fatalf("duplicate datadir failure mismatch: have %v, want %v", err, ErrDatadirUsed)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests whether services can be registered and duplicates caught.
|
||||
func TestServiceRegistry(t *testing.T) {
|
||||
stack, err := New(testNodeConfig())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create protocol stack: %v", err)
|
||||
}
|
||||
// Register a batch of unique services and ensure they start successfully
|
||||
services := []ServiceConstructor{NewNoopServiceA, NewNoopServiceB, NewNoopServiceC}
|
||||
for i, constructor := range services {
|
||||
if err := stack.Register(constructor); err != nil {
|
||||
t.Fatalf("service #%d: registration failed: %v", i, err)
|
||||
}
|
||||
}
|
||||
if err := stack.Start(); err != nil {
|
||||
t.Fatalf("failed to start original service stack: %v", err)
|
||||
}
|
||||
if err := stack.Stop(); err != nil {
|
||||
t.Fatalf("failed to stop original service stack: %v", err)
|
||||
}
|
||||
// Duplicate one of the services and retry starting the node
|
||||
if err := stack.Register(NewNoopServiceB); err != nil {
|
||||
t.Fatalf("duplicate registration failed: %v", err)
|
||||
}
|
||||
if err := stack.Start(); err == nil {
|
||||
t.Fatalf("duplicate service started")
|
||||
} else {
|
||||
if _, ok := err.(*DuplicateServiceError); !ok {
|
||||
t.Fatalf("duplicate error mismatch: have %v, want %v", err, DuplicateServiceError{})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that registered services get started and stopped correctly.
|
||||
func TestServiceLifeCycle(t *testing.T) {
|
||||
stack, err := New(testNodeConfig())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create protocol stack: %v", err)
|
||||
}
|
||||
// Register a batch of life-cycle instrumented services
|
||||
services := map[string]InstrumentingWrapper{
|
||||
"A": InstrumentedServiceMakerA,
|
||||
"B": InstrumentedServiceMakerB,
|
||||
"C": InstrumentedServiceMakerC,
|
||||
}
|
||||
started := make(map[string]bool)
|
||||
stopped := make(map[string]bool)
|
||||
|
||||
for id, maker := range services {
|
||||
id := id // Closure for the constructor
|
||||
constructor := func(*ServiceContext) (Service, error) {
|
||||
return &InstrumentedService{
|
||||
startHook: func(*p2p.Server) { started[id] = true },
|
||||
stopHook: func() { stopped[id] = true },
|
||||
}, nil
|
||||
}
|
||||
if err := stack.Register(maker(constructor)); err != nil {
|
||||
t.Fatalf("service %s: registration failed: %v", id, err)
|
||||
}
|
||||
}
|
||||
// Start the node and check that all services are running
|
||||
if err := stack.Start(); err != nil {
|
||||
t.Fatalf("failed to start protocol stack: %v", err)
|
||||
}
|
||||
for id := range services {
|
||||
if !started[id] {
|
||||
t.Fatalf("service %s: freshly started service not running", id)
|
||||
}
|
||||
if stopped[id] {
|
||||
t.Fatalf("service %s: freshly started service already stopped", id)
|
||||
}
|
||||
}
|
||||
// Stop the node and check that all services have been stopped
|
||||
if err := stack.Stop(); err != nil {
|
||||
t.Fatalf("failed to stop protocol stack: %v", err)
|
||||
}
|
||||
for id := range services {
|
||||
if !stopped[id] {
|
||||
t.Fatalf("service %s: freshly terminated service still running", id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that services are restarted cleanly as new instances.
|
||||
func TestServiceRestarts(t *testing.T) {
|
||||
stack, err := New(testNodeConfig())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create protocol stack: %v", err)
|
||||
}
|
||||
// Define a service that does not support restarts
|
||||
var (
|
||||
running bool
|
||||
started int
|
||||
)
|
||||
constructor := func(*ServiceContext) (Service, error) {
|
||||
running = false
|
||||
|
||||
return &InstrumentedService{
|
||||
startHook: func(*p2p.Server) {
|
||||
if running {
|
||||
panic("already running")
|
||||
}
|
||||
running = true
|
||||
started++
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
// Register the service and start the protocol stack
|
||||
if err := stack.Register(constructor); err != nil {
|
||||
t.Fatalf("failed to register the service: %v", err)
|
||||
}
|
||||
if err := stack.Start(); err != nil {
|
||||
t.Fatalf("failed to start protocol stack: %v", err)
|
||||
}
|
||||
defer stack.Stop()
|
||||
|
||||
if !running || started != 1 {
|
||||
t.Fatalf("running/started mismatch: have %v/%d, want true/1", running, started)
|
||||
}
|
||||
// Restart the stack a few times and check successful service restarts
|
||||
for i := 0; i < 3; i++ {
|
||||
if err := stack.Restart(); err != nil {
|
||||
t.Fatalf("iter %d: failed to restart stack: %v", i, err)
|
||||
}
|
||||
}
|
||||
if !running || started != 4 {
|
||||
t.Fatalf("running/started mismatch: have %v/%d, want true/4", running, started)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that if a service fails to initialize itself, none of the other services
|
||||
// will be allowed to even start.
|
||||
func TestServiceConstructionAbortion(t *testing.T) {
|
||||
stack, err := New(testNodeConfig())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create protocol stack: %v", err)
|
||||
}
|
||||
// Define a batch of good services
|
||||
services := map[string]InstrumentingWrapper{
|
||||
"A": InstrumentedServiceMakerA,
|
||||
"B": InstrumentedServiceMakerB,
|
||||
"C": InstrumentedServiceMakerC,
|
||||
}
|
||||
started := make(map[string]bool)
|
||||
for id, maker := range services {
|
||||
id := id // Closure for the constructor
|
||||
constructor := func(*ServiceContext) (Service, error) {
|
||||
return &InstrumentedService{
|
||||
startHook: func(*p2p.Server) { started[id] = true },
|
||||
}, nil
|
||||
}
|
||||
if err := stack.Register(maker(constructor)); err != nil {
|
||||
t.Fatalf("service %s: registration failed: %v", id, err)
|
||||
}
|
||||
}
|
||||
// Register a service that fails to construct itself
|
||||
failure := errors.New("fail")
|
||||
failer := func(*ServiceContext) (Service, error) {
|
||||
return nil, failure
|
||||
}
|
||||
if err := stack.Register(failer); err != nil {
|
||||
t.Fatalf("failer registration failed: %v", err)
|
||||
}
|
||||
// Start the protocol stack and ensure none of the services get started
|
||||
for i := 0; i < 100; i++ {
|
||||
if err := stack.Start(); err != failure {
|
||||
t.Fatalf("iter %d: stack startup failure mismatch: have %v, want %v", i, err, failure)
|
||||
}
|
||||
for id := range services {
|
||||
if started[id] {
|
||||
t.Fatalf("service %s: started should not have", id)
|
||||
}
|
||||
delete(started, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that if a service fails to start, all others started before it will be
|
||||
// shut down.
|
||||
func TestServiceStartupAbortion(t *testing.T) {
|
||||
stack, err := New(testNodeConfig())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create protocol stack: %v", err)
|
||||
}
|
||||
// Register a batch of good services
|
||||
services := map[string]InstrumentingWrapper{
|
||||
"A": InstrumentedServiceMakerA,
|
||||
"B": InstrumentedServiceMakerB,
|
||||
"C": InstrumentedServiceMakerC,
|
||||
}
|
||||
started := make(map[string]bool)
|
||||
stopped := make(map[string]bool)
|
||||
|
||||
for id, maker := range services {
|
||||
id := id // Closure for the constructor
|
||||
constructor := func(*ServiceContext) (Service, error) {
|
||||
return &InstrumentedService{
|
||||
startHook: func(*p2p.Server) { started[id] = true },
|
||||
stopHook: func() { stopped[id] = true },
|
||||
}, nil
|
||||
}
|
||||
if err := stack.Register(maker(constructor)); err != nil {
|
||||
t.Fatalf("service %s: registration failed: %v", id, err)
|
||||
}
|
||||
}
|
||||
// Register a service that fails to start
|
||||
failure := errors.New("fail")
|
||||
failer := func(*ServiceContext) (Service, error) {
|
||||
return &InstrumentedService{
|
||||
start: failure,
|
||||
}, nil
|
||||
}
|
||||
if err := stack.Register(failer); err != nil {
|
||||
t.Fatalf("failer registration failed: %v", err)
|
||||
}
|
||||
// Start the protocol stack and ensure all started services stop
|
||||
for i := 0; i < 100; i++ {
|
||||
if err := stack.Start(); err != failure {
|
||||
t.Fatalf("iter %d: stack startup failure mismatch: have %v, want %v", i, err, failure)
|
||||
}
|
||||
for id := range services {
|
||||
if started[id] && !stopped[id] {
|
||||
t.Fatalf("service %s: started but not stopped", id)
|
||||
}
|
||||
delete(started, id)
|
||||
delete(stopped, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that even if a registered service fails to shut down cleanly, it does
|
||||
// not influece the rest of the shutdown invocations.
|
||||
func TestServiceTerminationGuarantee(t *testing.T) {
|
||||
stack, err := New(testNodeConfig())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create protocol stack: %v", err)
|
||||
}
|
||||
// Register a batch of good services
|
||||
services := map[string]InstrumentingWrapper{
|
||||
"A": InstrumentedServiceMakerA,
|
||||
"B": InstrumentedServiceMakerB,
|
||||
"C": InstrumentedServiceMakerC,
|
||||
}
|
||||
started := make(map[string]bool)
|
||||
stopped := make(map[string]bool)
|
||||
|
||||
for id, maker := range services {
|
||||
id := id // Closure for the constructor
|
||||
constructor := func(*ServiceContext) (Service, error) {
|
||||
return &InstrumentedService{
|
||||
startHook: func(*p2p.Server) { started[id] = true },
|
||||
stopHook: func() { stopped[id] = true },
|
||||
}, nil
|
||||
}
|
||||
if err := stack.Register(maker(constructor)); err != nil {
|
||||
t.Fatalf("service %s: registration failed: %v", id, err)
|
||||
}
|
||||
}
|
||||
// Register a service that fails to shot down cleanly
|
||||
failure := errors.New("fail")
|
||||
failer := func(*ServiceContext) (Service, error) {
|
||||
return &InstrumentedService{
|
||||
stop: failure,
|
||||
}, nil
|
||||
}
|
||||
if err := stack.Register(failer); err != nil {
|
||||
t.Fatalf("failer registration failed: %v", err)
|
||||
}
|
||||
// Start the protocol stack, and ensure that a failing shut down terminates all
|
||||
for i := 0; i < 100; i++ {
|
||||
// Start the stack and make sure all is online
|
||||
if err := stack.Start(); err != nil {
|
||||
t.Fatalf("iter %d: failed to start protocol stack: %v", i, err)
|
||||
}
|
||||
for id := range services {
|
||||
if !started[id] {
|
||||
t.Fatalf("iter %d, service %s: service not running", i, id)
|
||||
}
|
||||
if stopped[id] {
|
||||
t.Fatalf("iter %d, service %s: service already stopped", i, id)
|
||||
}
|
||||
}
|
||||
// Stop the stack, verify failure and check all terminations
|
||||
err := stack.Stop()
|
||||
if err, ok := err.(*StopError); !ok {
|
||||
t.Fatalf("iter %d: termination failure mismatch: have %v, want StopError", i, err)
|
||||
} else {
|
||||
failer := reflect.TypeOf(&InstrumentedService{})
|
||||
if err.Services[failer] != failure {
|
||||
t.Fatalf("iter %d: failer termination failure mismatch: have %v, want %v", i, err.Services[failer], failure)
|
||||
}
|
||||
if len(err.Services) != 1 {
|
||||
t.Fatalf("iter %d: failure count mismatch: have %d, want %d", i, len(err.Services), 1)
|
||||
}
|
||||
}
|
||||
for id := range services {
|
||||
if !stopped[id] {
|
||||
t.Fatalf("iter %d, service %s: service not terminated", i, id)
|
||||
}
|
||||
delete(started, id)
|
||||
delete(stopped, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestServiceRetrieval tests that individual services can be retrieved.
|
||||
func TestServiceRetrieval(t *testing.T) {
|
||||
// Create a simple stack and register two service types
|
||||
stack, err := New(testNodeConfig())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create protocol stack: %v", err)
|
||||
}
|
||||
if err := stack.Register(NewNoopService); err != nil {
|
||||
t.Fatalf("noop service registration failed: %v", err)
|
||||
}
|
||||
if err := stack.Register(NewInstrumentedService); err != nil {
|
||||
t.Fatalf("instrumented service registration failed: %v", err)
|
||||
}
|
||||
// Make sure none of the services can be retrieved until started
|
||||
var noopServ *NoopService
|
||||
if err := stack.Service(&noopServ); err != ErrNodeStopped {
|
||||
t.Fatalf("noop service retrieval mismatch: have %v, want %v", err, ErrNodeStopped)
|
||||
}
|
||||
var instServ *InstrumentedService
|
||||
if err := stack.Service(&instServ); err != ErrNodeStopped {
|
||||
t.Fatalf("instrumented service retrieval mismatch: have %v, want %v", err, ErrNodeStopped)
|
||||
}
|
||||
// Start the stack and ensure everything is retrievable now
|
||||
if err := stack.Start(); err != nil {
|
||||
t.Fatalf("failed to start stack: %v", err)
|
||||
}
|
||||
defer stack.Stop()
|
||||
|
||||
if err := stack.Service(&noopServ); err != nil {
|
||||
t.Fatalf("noop service retrieval mismatch: have %v, want %v", err, nil)
|
||||
}
|
||||
if err := stack.Service(&instServ); err != nil {
|
||||
t.Fatalf("instrumented service retrieval mismatch: have %v, want %v", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that all protocols defined by individual services get launched.
|
||||
func TestProtocolGather(t *testing.T) {
|
||||
stack, err := New(testNodeConfig())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create protocol stack: %v", err)
|
||||
}
|
||||
// Register a batch of services with some configured number of protocols
|
||||
services := map[string]struct {
|
||||
Count int
|
||||
Maker InstrumentingWrapper
|
||||
}{
|
||||
"Zero Protocols": {0, InstrumentedServiceMakerA},
|
||||
"Single Protocol": {1, InstrumentedServiceMakerB},
|
||||
"Many Protocols": {25, InstrumentedServiceMakerC},
|
||||
}
|
||||
for id, config := range services {
|
||||
protocols := make([]p2p.Protocol, config.Count)
|
||||
for i := 0; i < len(protocols); i++ {
|
||||
protocols[i].Name = id
|
||||
protocols[i].Version = uint(i)
|
||||
}
|
||||
constructor := func(*ServiceContext) (Service, error) {
|
||||
return &InstrumentedService{
|
||||
protocols: protocols,
|
||||
}, nil
|
||||
}
|
||||
if err := stack.Register(config.Maker(constructor)); err != nil {
|
||||
t.Fatalf("service %s: registration failed: %v", id, err)
|
||||
}
|
||||
}
|
||||
// Start the services and ensure all protocols start successfully
|
||||
if err := stack.Start(); err != nil {
|
||||
t.Fatalf("failed to start protocol stack: %v", err)
|
||||
}
|
||||
defer stack.Stop()
|
||||
|
||||
protocols := stack.Server().Protocols
|
||||
if len(protocols) != 26 {
|
||||
t.Fatalf("mismatching number of protocols launched: have %d, want %d", len(protocols), 26)
|
||||
}
|
||||
for id, config := range services {
|
||||
for ver := 0; ver < config.Count; ver++ {
|
||||
launched := false
|
||||
for i := 0; i < len(protocols); i++ {
|
||||
if protocols[i].Name == id && protocols[i].Version == uint(ver) {
|
||||
launched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !launched {
|
||||
t.Errorf("configured protocol not launched: %s v%d", id, ver)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that all APIs defined by individual services get exposed.
|
||||
func TestAPIGather(t *testing.T) {
|
||||
stack, err := New(testNodeConfig())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create protocol stack: %v", err)
|
||||
}
|
||||
// Register a batch of services with some configured APIs
|
||||
calls := make(chan string, 1)
|
||||
makeAPI := func(result string) *OneMethodApi {
|
||||
return &OneMethodApi{fun: func() { calls <- result }}
|
||||
}
|
||||
services := map[string]struct {
|
||||
APIs []rpc.API
|
||||
Maker InstrumentingWrapper
|
||||
}{
|
||||
"Zero APIs": {
|
||||
[]rpc.API{}, InstrumentedServiceMakerA},
|
||||
"Single API": {
|
||||
[]rpc.API{
|
||||
{Namespace: "single", Version: "1", Service: makeAPI("single.v1"), Public: true},
|
||||
}, InstrumentedServiceMakerB},
|
||||
"Many APIs": {
|
||||
[]rpc.API{
|
||||
{Namespace: "multi", Version: "1", Service: makeAPI("multi.v1"), Public: true},
|
||||
{Namespace: "multi.v2", Version: "2", Service: makeAPI("multi.v2"), Public: true},
|
||||
{Namespace: "multi.v2.nested", Version: "2", Service: makeAPI("multi.v2.nested"), Public: true},
|
||||
}, InstrumentedServiceMakerC},
|
||||
}
|
||||
|
||||
for id, config := range services {
|
||||
config := config
|
||||
constructor := func(*ServiceContext) (Service, error) {
|
||||
return &InstrumentedService{apis: config.APIs}, nil
|
||||
}
|
||||
if err := stack.Register(config.Maker(constructor)); err != nil {
|
||||
t.Fatalf("service %s: registration failed: %v", id, err)
|
||||
}
|
||||
}
|
||||
// Start the services and ensure all API start successfully
|
||||
if err := stack.Start(); err != nil {
|
||||
t.Fatalf("failed to start protocol stack: %v", err)
|
||||
}
|
||||
defer stack.Stop()
|
||||
|
||||
// Connect to the RPC server and verify the various registered endpoints
|
||||
client, err := stack.Attach()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to connect to the inproc API server: %v", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
tests := []struct {
|
||||
Method string
|
||||
Result string
|
||||
}{
|
||||
{"single_theOneMethod", "single.v1"},
|
||||
{"multi_theOneMethod", "multi.v1"},
|
||||
{"multi.v2_theOneMethod", "multi.v2"},
|
||||
{"multi.v2.nested_theOneMethod", "multi.v2.nested"},
|
||||
}
|
||||
for i, test := range tests {
|
||||
if err := client.Call(nil, test.Method); err != nil {
|
||||
t.Errorf("test %d: API request failed: %v", i, err)
|
||||
}
|
||||
select {
|
||||
case result := <-calls:
|
||||
if result != test.Result {
|
||||
t.Errorf("test %d: result mismatch: have %s, want %s", i, result, test.Result)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatalf("test %d: rpc execution timeout", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
// 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 (
|
||||
"reflect"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// ServiceContext is a collection of service independent options inherited from
|
||||
// the protocol stack, that is passed to all constructors to be optionally used;
|
||||
// as well as utility methods to operate on the service environment.
|
||||
type ServiceContext struct {
|
||||
config *Config
|
||||
services map[reflect.Type]Service // Index of the already constructed services
|
||||
EventMux *event.TypeMux // Event multiplexer used for decoupled notifications
|
||||
AccountManager *accounts.Manager // Account manager created by the node.
|
||||
}
|
||||
|
||||
// OpenDatabase opens an existing database with the given name (or creates one
|
||||
// if no previous can be found) from within the node's data directory. If the
|
||||
// node is an ephemeral one, a memory database is returned.
|
||||
func (ctx *ServiceContext) OpenDatabase(name string, cache int, handles int) (ethdb.Database, error) {
|
||||
if ctx.config.DataDir == "" {
|
||||
return ethdb.NewMemDatabase()
|
||||
}
|
||||
db, err := ethdb.NewLDBDatabase(ctx.config.resolvePath(name), cache, handles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// ResolvePath resolves a user path into the data directory if that was relative
|
||||
// and if the user actually uses persistent storage. It will return an empty string
|
||||
// for emphemeral storage and the user's own input for absolute paths.
|
||||
func (ctx *ServiceContext) ResolvePath(path string) string {
|
||||
return ctx.config.resolvePath(path)
|
||||
}
|
||||
|
||||
// Service retrieves a currently running service registered of a specific type.
|
||||
func (ctx *ServiceContext) Service(service interface{}) error {
|
||||
element := reflect.ValueOf(service).Elem()
|
||||
if running, ok := ctx.services[element.Type()]; ok {
|
||||
element.Set(reflect.ValueOf(running))
|
||||
return nil
|
||||
}
|
||||
return ErrServiceUnknown
|
||||
}
|
||||
|
||||
// ServiceConstructor is the function signature of the constructors needed to be
|
||||
// registered for service instantiation.
|
||||
type ServiceConstructor func(ctx *ServiceContext) (Service, error)
|
||||
|
||||
// Service is an individual protocol that can be registered into a node.
|
||||
//
|
||||
// Notes:
|
||||
//
|
||||
// • Service life-cycle management is delegated to the node. The service is allowed to
|
||||
// initialize itself upon creation, but no goroutines should be spun up outside of the
|
||||
// Start method.
|
||||
//
|
||||
// • Restart logic is not required as the node will create a fresh instance
|
||||
// every time a service is started.
|
||||
type Service interface {
|
||||
// Protocols retrieves the P2P protocols the service wishes to start.
|
||||
Protocols() []p2p.Protocol
|
||||
|
||||
// APIs retrieves the list of RPC descriptors the service provides
|
||||
APIs() []rpc.API
|
||||
|
||||
// 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(server *p2p.Server) error
|
||||
|
||||
// Stop terminates all goroutines belonging to the service, blocking until they
|
||||
// are all terminated.
|
||||
Stop() error
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Tests that databases are correctly created persistent or ephemeral based on
|
||||
// the configured service context.
|
||||
func TestContextDatabases(t *testing.T) {
|
||||
// Create a temporary folder and ensure no database is contained within
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temporary data directory: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
if _, err := os.Stat(filepath.Join(dir, "database")); err == nil {
|
||||
t.Fatalf("non-created database already exists")
|
||||
}
|
||||
// Request the opening/creation of a database and ensure it persists to disk
|
||||
ctx := &ServiceContext{config: &Config{Name: "unit-test", DataDir: dir}}
|
||||
db, err := ctx.OpenDatabase("persistent", 0, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open persistent database: %v", err)
|
||||
}
|
||||
db.Close()
|
||||
|
||||
if _, err := os.Stat(filepath.Join(dir, "unit-test", "persistent")); err != nil {
|
||||
t.Fatalf("persistent database doesn't exists: %v", err)
|
||||
}
|
||||
// Request th opening/creation of an ephemeral database and ensure it's not persisted
|
||||
ctx = &ServiceContext{config: &Config{DataDir: ""}}
|
||||
db, err = ctx.OpenDatabase("ephemeral", 0, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open ephemeral database: %v", err)
|
||||
}
|
||||
db.Close()
|
||||
|
||||
if _, err := os.Stat(filepath.Join(dir, "ephemeral")); err == nil {
|
||||
t.Fatalf("ephemeral database exists")
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that already constructed services can be retrieves by later ones.
|
||||
func TestContextServices(t *testing.T) {
|
||||
stack, err := New(testNodeConfig())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create protocol stack: %v", err)
|
||||
}
|
||||
// Define a verifier that ensures a NoopA is before it and NoopB after
|
||||
verifier := func(ctx *ServiceContext) (Service, error) {
|
||||
var objA *NoopServiceA
|
||||
if ctx.Service(&objA) != nil {
|
||||
return nil, fmt.Errorf("former service not found")
|
||||
}
|
||||
var objB *NoopServiceB
|
||||
if err := ctx.Service(&objB); err != ErrServiceUnknown {
|
||||
return nil, fmt.Errorf("latters lookup error mismatch: have %v, want %v", err, ErrServiceUnknown)
|
||||
}
|
||||
return new(NoopService), nil
|
||||
}
|
||||
// Register the collection of services
|
||||
if err := stack.Register(NewNoopServiceA); err != nil {
|
||||
t.Fatalf("former failed to register service: %v", err)
|
||||
}
|
||||
if err := stack.Register(verifier); err != nil {
|
||||
t.Fatalf("failed to register service verifier: %v", err)
|
||||
}
|
||||
if err := stack.Register(NewNoopServiceB); err != nil {
|
||||
t.Fatalf("latter failed to register service: %v", err)
|
||||
}
|
||||
// Start the protocol stack and ensure services are constructed in order
|
||||
if err := stack.Start(); err != nil {
|
||||
t.Fatalf("failed to start stack: %v", err)
|
||||
}
|
||||
defer stack.Stop()
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
// 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/>.
|
||||
|
||||
// Contains a batch of utility type declarations used by the tests. As the node
|
||||
// operates on unique types, a lot of them are needed to check various features.
|
||||
|
||||
package node
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// NoopService is a trivial implementation of the Service interface.
|
||||
type NoopService struct{}
|
||||
|
||||
func (s *NoopService) Protocols() []p2p.Protocol { return nil }
|
||||
func (s *NoopService) APIs() []rpc.API { return nil }
|
||||
func (s *NoopService) Start(*p2p.Server) error { return nil }
|
||||
func (s *NoopService) Stop() error { return nil }
|
||||
|
||||
func NewNoopService(*ServiceContext) (Service, error) { return new(NoopService), nil }
|
||||
|
||||
// Set of services all wrapping the base NoopService resulting in the same method
|
||||
// signatures but different outer types.
|
||||
type NoopServiceA struct{ NoopService }
|
||||
type NoopServiceB struct{ NoopService }
|
||||
type NoopServiceC struct{ NoopService }
|
||||
|
||||
func NewNoopServiceA(*ServiceContext) (Service, error) { return new(NoopServiceA), nil }
|
||||
func NewNoopServiceB(*ServiceContext) (Service, error) { return new(NoopServiceB), nil }
|
||||
func NewNoopServiceC(*ServiceContext) (Service, error) { return new(NoopServiceC), nil }
|
||||
|
||||
// InstrumentedService is an implementation of Service for which all interface
|
||||
// methods can be instrumented both return value as well as event hook wise.
|
||||
type InstrumentedService struct {
|
||||
protocols []p2p.Protocol
|
||||
apis []rpc.API
|
||||
start error
|
||||
stop error
|
||||
|
||||
protocolsHook func()
|
||||
startHook func(*p2p.Server)
|
||||
stopHook func()
|
||||
}
|
||||
|
||||
func NewInstrumentedService(*ServiceContext) (Service, error) { return new(InstrumentedService), nil }
|
||||
|
||||
func (s *InstrumentedService) Protocols() []p2p.Protocol {
|
||||
if s.protocolsHook != nil {
|
||||
s.protocolsHook()
|
||||
}
|
||||
return s.protocols
|
||||
}
|
||||
|
||||
func (s *InstrumentedService) APIs() []rpc.API {
|
||||
return s.apis
|
||||
}
|
||||
|
||||
func (s *InstrumentedService) Start(server *p2p.Server) error {
|
||||
if s.startHook != nil {
|
||||
s.startHook(server)
|
||||
}
|
||||
return s.start
|
||||
}
|
||||
|
||||
func (s *InstrumentedService) Stop() error {
|
||||
if s.stopHook != nil {
|
||||
s.stopHook()
|
||||
}
|
||||
return s.stop
|
||||
}
|
||||
|
||||
// InstrumentingWrapper is a method to specialize a service constructor returning
|
||||
// a generic InstrumentedService into one returning a wrapping specific one.
|
||||
type InstrumentingWrapper func(base ServiceConstructor) ServiceConstructor
|
||||
|
||||
func InstrumentingWrapperMaker(base ServiceConstructor, kind reflect.Type) ServiceConstructor {
|
||||
return func(ctx *ServiceContext) (Service, error) {
|
||||
obj, err := base(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wrapper := reflect.New(kind)
|
||||
wrapper.Elem().Field(0).Set(reflect.ValueOf(obj).Elem())
|
||||
|
||||
return wrapper.Interface().(Service), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Set of services all wrapping the base InstrumentedService resulting in the
|
||||
// same method signatures but different outer types.
|
||||
type InstrumentedServiceA struct{ InstrumentedService }
|
||||
type InstrumentedServiceB struct{ InstrumentedService }
|
||||
type InstrumentedServiceC struct{ InstrumentedService }
|
||||
|
||||
func InstrumentedServiceMakerA(base ServiceConstructor) ServiceConstructor {
|
||||
return InstrumentingWrapperMaker(base, reflect.TypeOf(InstrumentedServiceA{}))
|
||||
}
|
||||
|
||||
func InstrumentedServiceMakerB(base ServiceConstructor) ServiceConstructor {
|
||||
return InstrumentingWrapperMaker(base, reflect.TypeOf(InstrumentedServiceB{}))
|
||||
}
|
||||
|
||||
func InstrumentedServiceMakerC(base ServiceConstructor) ServiceConstructor {
|
||||
return InstrumentingWrapperMaker(base, reflect.TypeOf(InstrumentedServiceC{}))
|
||||
}
|
||||
|
||||
// OneMethodApi is a single-method API handler to be returned by test services.
|
||||
type OneMethodApi struct {
|
||||
fun func()
|
||||
}
|
||||
|
||||
func (api *OneMethodApi) TheOneMethod() {
|
||||
if api.fun != nil {
|
||||
api.fun()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user