forked from ethereum/go-ethereum
-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
node: customizable protocol and service stacks
- Loading branch information
Showing
13 changed files
with
1,333 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
// 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 ( | ||
"crypto/ecdsa" | ||
"encoding/json" | ||
"io/ioutil" | ||
"net" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/ethereum/go-ethereum/crypto" | ||
"github.com/ethereum/go-ethereum/logger" | ||
"github.com/ethereum/go-ethereum/logger/glog" | ||
"github.com/ethereum/go-ethereum/p2p/discover" | ||
"github.com/ethereum/go-ethereum/p2p/nat" | ||
) | ||
|
||
var ( | ||
datadirPrivateKey = "nodekey" // Path within the datadir to the node's private key | ||
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 { | ||
// 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 | ||
|
||
// This field should be a valid secp256k1 private key that will be used for both | ||
// remote peer identification as well as network traffic encryption. If no key | ||
// is configured, the preset one is loaded from the data dir, generating it if | ||
// needed. | ||
PrivateKey *ecdsa.PrivateKey | ||
|
||
// Name sets the node name of this server. Use common.MakeName to create a name | ||
// that follows existing conventions. | ||
Name string | ||
|
||
// NoDiscovery specifies whether the peer discovery mechanism should be started | ||
// or not. Disabling is usually useful for protocol debugging (manual topology). | ||
NoDiscovery bool | ||
|
||
// Bootstrap nodes used to establish connectivity with the rest of the network. | ||
BootstrapNodes []*discover.Node | ||
|
||
// Network interface address on which the node should listen for inbound peers. | ||
ListenAddr string | ||
|
||
// If set to a non-nil value, the given NAT port mapper is used to make the | ||
// listening port available to the Internet. | ||
NAT nat.Interface | ||
|
||
// If Dialer is set to a non-nil value, the given Dialer is used to dial outbound | ||
// peer connections. | ||
Dialer *net.Dialer | ||
|
||
// If NoDial is true, the node will not dial any peers. | ||
NoDial bool | ||
|
||
// MaxPeers is the maximum number of peers that can be connected. If this is | ||
// set to zero, then only the configured static and trusted peers can connect. | ||
MaxPeers int | ||
|
||
// MaxPendingPeers is the maximum number of peers that can be pending in the | ||
// handshake phase, counted separately for inbound and outbound connections. | ||
// Zero defaults to preset values. | ||
MaxPendingPeers int | ||
} | ||
|
||
// 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.PrivateKey != nil { | ||
return c.PrivateKey | ||
} | ||
// Generate ephemeral key if no datadir is being used | ||
if c.DataDir == "" { | ||
key, err := crypto.GenerateKey() | ||
if err != nil { | ||
glog.Fatalf("Failed to generate ephemeral node key: %v", err) | ||
} | ||
return key | ||
} | ||
// Fall back to persistent key from the data directory | ||
keyfile := filepath.Join(c.DataDir, 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 { | ||
glog.Fatalf("Failed to generate node key: %v", err) | ||
} | ||
if err := crypto.SaveECDSA(keyfile, key); err != nil { | ||
glog.V(logger.Error).Infof("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(datadirStaticNodes) | ||
} | ||
|
||
// TrusterNodes returns a list of node enode URLs configured as trusted nodes. | ||
func (c *Config) TrusterNodes() []*discover.Node { | ||
return c.parsePersistentNodes(datadirTrustedNodes) | ||
} | ||
|
||
// parsePersistentNodes parses a list of discovery node URLs loaded from a .json | ||
// file from within the data directory. | ||
func (c *Config) parsePersistentNodes(file string) []*discover.Node { | ||
// Short circuit if no node config is present | ||
if c.DataDir == "" { | ||
return nil | ||
} | ||
path := filepath.Join(c.DataDir, file) | ||
if _, err := os.Stat(path); err != nil { | ||
return nil | ||
} | ||
// Load the nodes from the config file | ||
blob, err := ioutil.ReadFile(path) | ||
if err != nil { | ||
glog.V(logger.Error).Infof("Failed to access nodes: %v", err) | ||
return nil | ||
} | ||
nodelist := []string{} | ||
if err := json.Unmarshal(blob, &nodelist); err != nil { | ||
glog.V(logger.Error).Infof("Failed to load nodes: %v", 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 { | ||
glog.V(logger.Error).Infof("Node URL %s: %v\n", url, err) | ||
continue | ||
} | ||
nodes = append(nodes, node) | ||
} | ||
return nodes | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
// 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" | ||
"testing" | ||
|
||
"github.com/ethereum/go-ethereum/crypto" | ||
) | ||
|
||
// 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 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("", "") | ||
if err != nil { | ||
t.Fatalf("failed to create temporary data directory: %v", err) | ||
} | ||
defer os.RemoveAll(dir) | ||
|
||
if _, err := os.Stat(filepath.Join(dir, datadirPrivateKey)); err == nil { | ||
t.Fatalf("non-created node key already exists") | ||
} | ||
// 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) | ||
} | ||
if _, err := New(&Config{DataDir: dir, PrivateKey: key}); err != nil { | ||
t.Fatalf("failed to create empty stack: %v", err) | ||
} | ||
if _, err := os.Stat(filepath.Join(dir, datadirPrivateKey)); 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 | ||
if _, err := New(&Config{DataDir: dir}); err != nil { | ||
t.Fatalf("failed to create newly keyed stack: %v", err) | ||
} | ||
if _, err := os.Stat(filepath.Join(dir, datadirPrivateKey)); err != nil { | ||
t.Fatalf("node key not persisted to data directory: %v", err) | ||
} | ||
key, err = crypto.LoadECDSA(filepath.Join(dir, datadirPrivateKey)) | ||
if err != nil { | ||
t.Fatalf("failed to load freshly persisted node key: %v", err) | ||
} | ||
blob1, err := ioutil.ReadFile(filepath.Join(dir, datadirPrivateKey)) | ||
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 | ||
if _, err := New(&Config{DataDir: dir}); err != nil { | ||
t.Fatalf("failed to create previously keyed stack: %v", err) | ||
} | ||
blob2, err := ioutil.ReadFile(filepath.Join(dir, datadirPrivateKey)) | ||
if err != nil { | ||
t.Fatalf("failed to read previously persisted node key: %v", err) | ||
} | ||
if bytes.Compare(blob1, blob2) != 0 { | ||
t.Fatalf("persisted node key mismatch: have %x, want %x", blob2, blob1) | ||
} | ||
// Configure ephemeral node and ensure no key is dumped locally | ||
if _, err := New(&Config{DataDir: ""}); err != nil { | ||
t.Fatalf("failed to create ephemeral stack: %v", err) | ||
} | ||
if _, err := os.Stat(filepath.Join(".", datadirPrivateKey)); err == nil { | ||
t.Fatalf("ephemeral node key persisted to disk") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// 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" | ||
|
||
// StopError is returned if a node fails to stop either any of its registered | ||
// services or itself. | ||
type StopError struct { | ||
Server error | ||
Services map[string]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) | ||
} |
Oops, something went wrong.