Skip to content

Commit 9e1d9bf

Browse files
committed
node: customizable protocol and service stacks
1 parent 168d0e9 commit 9e1d9bf

File tree

13 files changed

+1333
-13
lines changed

13 files changed

+1333
-13
lines changed

node/config.go

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// Copyright 2015 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package node
18+
19+
import (
20+
"crypto/ecdsa"
21+
"encoding/json"
22+
"io/ioutil"
23+
"net"
24+
"os"
25+
"path/filepath"
26+
27+
"github.com/ethereum/go-ethereum/crypto"
28+
"github.com/ethereum/go-ethereum/logger"
29+
"github.com/ethereum/go-ethereum/logger/glog"
30+
"github.com/ethereum/go-ethereum/p2p/discover"
31+
"github.com/ethereum/go-ethereum/p2p/nat"
32+
)
33+
34+
var (
35+
datadirPrivateKey = "nodekey" // Path within the datadir to the node's private key
36+
datadirStaticNodes = "static-nodes.json" // Path within the datadir to the static node list
37+
datadirTrustedNodes = "trusted-nodes.json" // Path within the datadir to the trusted node list
38+
datadirNodeDatabase = "nodes" // Path within the datadir to store the node infos
39+
)
40+
41+
// Config represents a small collection of configuration values to fine tune the
42+
// P2P network layer of a protocol stack. These values can be further extended by
43+
// all registered services.
44+
type Config struct {
45+
// DataDir is the file system folder the node should use for any data storage
46+
// requirements. The configured data directory will not be directly shared with
47+
// registered services, instead those can use utility methods to create/access
48+
// databases or flat files. This enables ephemeral nodes which can fully reside
49+
// in memory.
50+
DataDir string
51+
52+
// This field should be a valid secp256k1 private key that will be used for both
53+
// remote peer identification as well as network traffic encryption. If no key
54+
// is configured, the preset one is loaded from the data dir, generating it if
55+
// needed.
56+
PrivateKey *ecdsa.PrivateKey
57+
58+
// Name sets the node name of this server. Use common.MakeName to create a name
59+
// that follows existing conventions.
60+
Name string
61+
62+
// NoDiscovery specifies whether the peer discovery mechanism should be started
63+
// or not. Disabling is usually useful for protocol debugging (manual topology).
64+
NoDiscovery bool
65+
66+
// Bootstrap nodes used to establish connectivity with the rest of the network.
67+
BootstrapNodes []*discover.Node
68+
69+
// Network interface address on which the node should listen for inbound peers.
70+
ListenAddr string
71+
72+
// If set to a non-nil value, the given NAT port mapper is used to make the
73+
// listening port available to the Internet.
74+
NAT nat.Interface
75+
76+
// If Dialer is set to a non-nil value, the given Dialer is used to dial outbound
77+
// peer connections.
78+
Dialer *net.Dialer
79+
80+
// If NoDial is true, the node will not dial any peers.
81+
NoDial bool
82+
83+
// MaxPeers is the maximum number of peers that can be connected. If this is
84+
// set to zero, then only the configured static and trusted peers can connect.
85+
MaxPeers int
86+
87+
// MaxPendingPeers is the maximum number of peers that can be pending in the
88+
// handshake phase, counted separately for inbound and outbound connections.
89+
// Zero defaults to preset values.
90+
MaxPendingPeers int
91+
}
92+
93+
// NodeKey retrieves the currently configured private key of the node, checking
94+
// first any manually set key, falling back to the one found in the configured
95+
// data folder. If no key can be found, a new one is generated.
96+
func (c *Config) NodeKey() *ecdsa.PrivateKey {
97+
// Use any specifically configured key
98+
if c.PrivateKey != nil {
99+
return c.PrivateKey
100+
}
101+
// Generate ephemeral key if no datadir is being used
102+
if c.DataDir == "" {
103+
key, err := crypto.GenerateKey()
104+
if err != nil {
105+
glog.Fatalf("Failed to generate ephemeral node key: %v", err)
106+
}
107+
return key
108+
}
109+
// Fall back to persistent key from the data directory
110+
keyfile := filepath.Join(c.DataDir, datadirPrivateKey)
111+
if key, err := crypto.LoadECDSA(keyfile); err == nil {
112+
return key
113+
}
114+
// No persistent key found, generate and store a new one
115+
key, err := crypto.GenerateKey()
116+
if err != nil {
117+
glog.Fatalf("Failed to generate node key: %v", err)
118+
}
119+
if err := crypto.SaveECDSA(keyfile, key); err != nil {
120+
glog.V(logger.Error).Infof("Failed to persist node key: %v", err)
121+
}
122+
return key
123+
}
124+
125+
// StaticNodes returns a list of node enode URLs configured as static nodes.
126+
func (c *Config) StaticNodes() []*discover.Node {
127+
return c.parsePersistentNodes(datadirStaticNodes)
128+
}
129+
130+
// TrusterNodes returns a list of node enode URLs configured as trusted nodes.
131+
func (c *Config) TrusterNodes() []*discover.Node {
132+
return c.parsePersistentNodes(datadirTrustedNodes)
133+
}
134+
135+
// parsePersistentNodes parses a list of discovery node URLs loaded from a .json
136+
// file from within the data directory.
137+
func (c *Config) parsePersistentNodes(file string) []*discover.Node {
138+
// Short circuit if no node config is present
139+
if c.DataDir == "" {
140+
return nil
141+
}
142+
path := filepath.Join(c.DataDir, file)
143+
if _, err := os.Stat(path); err != nil {
144+
return nil
145+
}
146+
// Load the nodes from the config file
147+
blob, err := ioutil.ReadFile(path)
148+
if err != nil {
149+
glog.V(logger.Error).Infof("Failed to access nodes: %v", err)
150+
return nil
151+
}
152+
nodelist := []string{}
153+
if err := json.Unmarshal(blob, &nodelist); err != nil {
154+
glog.V(logger.Error).Infof("Failed to load nodes: %v", err)
155+
return nil
156+
}
157+
// Interpret the list as a discovery node array
158+
var nodes []*discover.Node
159+
for _, url := range nodelist {
160+
if url == "" {
161+
continue
162+
}
163+
node, err := discover.ParseNode(url)
164+
if err != nil {
165+
glog.V(logger.Error).Infof("Node URL %s: %v\n", url, err)
166+
continue
167+
}
168+
nodes = append(nodes, node)
169+
}
170+
return nodes
171+
}

node/config_test.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// Copyright 2015 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package node
18+
19+
import (
20+
"bytes"
21+
"io/ioutil"
22+
"os"
23+
"path/filepath"
24+
"testing"
25+
26+
"github.com/ethereum/go-ethereum/crypto"
27+
)
28+
29+
// Tests that datadirs can be successfully created, be them manually configured
30+
// ones or automatically generated temporary ones.
31+
func TestDatadirCreation(t *testing.T) {
32+
// Create a temporary data dir and check that it can be used by a node
33+
dir, err := ioutil.TempDir("", "")
34+
if err != nil {
35+
t.Fatalf("failed to create manual data dir: %v", err)
36+
}
37+
defer os.RemoveAll(dir)
38+
39+
if _, err := New(&Config{DataDir: dir}); err != nil {
40+
t.Fatalf("failed to create stack with existing datadir: %v", err)
41+
}
42+
// Generate a long non-existing datadir path and check that it gets created by a node
43+
dir = filepath.Join(dir, "a", "b", "c", "d", "e", "f")
44+
if _, err := New(&Config{DataDir: dir}); err != nil {
45+
t.Fatalf("failed to create stack with creatable datadir: %v", err)
46+
}
47+
if _, err := os.Stat(dir); err != nil {
48+
t.Fatalf("freshly created datadir not accessible: %v", err)
49+
}
50+
// Verify that an impossible datadir fails creation
51+
file, err := ioutil.TempFile("", "")
52+
if err != nil {
53+
t.Fatalf("failed to create temporary file: %v", err)
54+
}
55+
defer os.Remove(file.Name())
56+
57+
dir = filepath.Join(file.Name(), "invalid/path")
58+
if _, err := New(&Config{DataDir: dir}); err == nil {
59+
t.Fatalf("protocol stack created with an invalid datadir")
60+
}
61+
}
62+
63+
// Tests that node keys can be correctly created, persisted, loaded and/or made
64+
// ephemeral.
65+
func TestNodeKeyPersistency(t *testing.T) {
66+
// Create a temporary folder and make sure no key is present
67+
dir, err := ioutil.TempDir("", "")
68+
if err != nil {
69+
t.Fatalf("failed to create temporary data directory: %v", err)
70+
}
71+
defer os.RemoveAll(dir)
72+
73+
if _, err := os.Stat(filepath.Join(dir, datadirPrivateKey)); err == nil {
74+
t.Fatalf("non-created node key already exists")
75+
}
76+
// Configure a node with a preset key and ensure it's not persisted
77+
key, err := crypto.GenerateKey()
78+
if err != nil {
79+
t.Fatalf("failed to generate one-shot node key: %v", err)
80+
}
81+
if _, err := New(&Config{DataDir: dir, PrivateKey: key}); err != nil {
82+
t.Fatalf("failed to create empty stack: %v", err)
83+
}
84+
if _, err := os.Stat(filepath.Join(dir, datadirPrivateKey)); err == nil {
85+
t.Fatalf("one-shot node key persisted to data directory")
86+
}
87+
// Configure a node with no preset key and ensure it is persisted this time
88+
if _, err := New(&Config{DataDir: dir}); err != nil {
89+
t.Fatalf("failed to create newly keyed stack: %v", err)
90+
}
91+
if _, err := os.Stat(filepath.Join(dir, datadirPrivateKey)); err != nil {
92+
t.Fatalf("node key not persisted to data directory: %v", err)
93+
}
94+
key, err = crypto.LoadECDSA(filepath.Join(dir, datadirPrivateKey))
95+
if err != nil {
96+
t.Fatalf("failed to load freshly persisted node key: %v", err)
97+
}
98+
blob1, err := ioutil.ReadFile(filepath.Join(dir, datadirPrivateKey))
99+
if err != nil {
100+
t.Fatalf("failed to read freshly persisted node key: %v", err)
101+
}
102+
// Configure a new node and ensure the previously persisted key is loaded
103+
if _, err := New(&Config{DataDir: dir}); err != nil {
104+
t.Fatalf("failed to create previously keyed stack: %v", err)
105+
}
106+
blob2, err := ioutil.ReadFile(filepath.Join(dir, datadirPrivateKey))
107+
if err != nil {
108+
t.Fatalf("failed to read previously persisted node key: %v", err)
109+
}
110+
if bytes.Compare(blob1, blob2) != 0 {
111+
t.Fatalf("persisted node key mismatch: have %x, want %x", blob2, blob1)
112+
}
113+
// Configure ephemeral node and ensure no key is dumped locally
114+
if _, err := New(&Config{DataDir: ""}); err != nil {
115+
t.Fatalf("failed to create ephemeral stack: %v", err)
116+
}
117+
if _, err := os.Stat(filepath.Join(".", datadirPrivateKey)); err == nil {
118+
t.Fatalf("ephemeral node key persisted to disk")
119+
}
120+
}

node/errors.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2015 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package node
18+
19+
import "fmt"
20+
21+
// StopError is returned if a node fails to stop either any of its registered
22+
// services or itself.
23+
type StopError struct {
24+
Server error
25+
Services map[string]error
26+
}
27+
28+
// Error generates a textual representation of the stop error.
29+
func (e *StopError) Error() string {
30+
return fmt.Sprintf("server: %v, services: %v", e.Server, e.Services)
31+
}

0 commit comments

Comments
 (0)