aboutsummaryrefslogtreecommitdiffstats
path: root/p2p/simulations/adapters/types.go
blob: 31856b76dd8ab960f6dd2e5eef553cb7101182c5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
// Copyright 2017 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 adapters

import (
    "crypto/ecdsa"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "net"
    "os"
    "strconv"

    "github.com/docker/docker/pkg/reexec"
    "github.com/ethereum/go-ethereum/crypto"
    "github.com/ethereum/go-ethereum/node"
    "github.com/ethereum/go-ethereum/p2p"
    "github.com/ethereum/go-ethereum/p2p/enode"
    "github.com/ethereum/go-ethereum/rpc"
)

// Node represents a node in a simulation network which is created by a
// NodeAdapter, for example:
//
// * SimNode    - An in-memory node
// * ExecNode   - A child process node
// * DockerNode - A Docker container node
//
type Node interface {
    // Addr returns the node's address (e.g. an Enode URL)
    Addr() []byte

    // Client returns the RPC client which is created once the node is
    // up and running
    Client() (*rpc.Client, error)

    // ServeRPC serves RPC requests over the given connection
    ServeRPC(net.Conn) error

    // Start starts the node with the given snapshots
    Start(snapshots map[string][]byte) error

    // Stop stops the node
    Stop() error

    // NodeInfo returns information about the node
    NodeInfo() *p2p.NodeInfo

    // Snapshots creates snapshots of the running services
    Snapshots() (map[string][]byte, error)
}

// NodeAdapter is used to create Nodes in a simulation network
type NodeAdapter interface {
    // Name returns the name of the adapter for logging purposes
    Name() string

    // NewNode creates a new node with the given configuration
    NewNode(config *NodeConfig) (Node, error)
}

// NodeConfig is the configuration used to start a node in a simulation
// network
type NodeConfig struct {
    // ID is the node's ID which is used to identify the node in the
    // simulation network
    ID enode.ID

    // PrivateKey is the node's private key which is used by the devp2p
    // stack to encrypt communications
    PrivateKey *ecdsa.PrivateKey

    // Enable peer events for Msgs
    EnableMsgEvents bool

    // Name is a human friendly name for the node like "node01"
    Name string

    // Use an existing database instead of a temporary one if non-empty
    DataDir string

    // Services are the names of the services which should be run when
    // starting the node (for SimNodes it should be the names of services
    // contained in SimAdapter.services, for other nodes it should be
    // services registered by calling the RegisterService function)
    Services []string

    // function to sanction or prevent suggesting a peer
    Reachable func(id enode.ID) bool

    Port uint16
}

// nodeConfigJSON is used to encode and decode NodeConfig as JSON by encoding
// all fields as strings
type nodeConfigJSON struct {
    ID              string   `json:"id"`
    PrivateKey      string   `json:"private_key"`
    Name            string   `json:"name"`
    Services        []string `json:"services"`
    EnableMsgEvents bool     `json:"enable_msg_events"`
    Port            uint16   `json:"port"`
}

// MarshalJSON implements the json.Marshaler interface by encoding the config
// fields as strings
func (n *NodeConfig) MarshalJSON() ([]byte, error) {
    confJSON := nodeConfigJSON{
        ID:              n.ID.String(),
        Name:            n.Name,
        Services:        n.Services,
        Port:            n.Port,
        EnableMsgEvents: n.EnableMsgEvents,
    }
    if n.PrivateKey != nil {
        confJSON.PrivateKey = hex.EncodeToString(crypto.FromECDSA(n.PrivateKey))
    }
    return json.Marshal(confJSON)
}

// UnmarshalJSON implements the json.Unmarshaler interface by decoding the json
// string values into the config fields
func (n *NodeConfig) UnmarshalJSON(data []byte) error {
    var confJSON nodeConfigJSON
    if err := json.Unmarshal(data, &confJSON); err != nil {
        return err
    }

    if confJSON.ID != "" {
        if err := n.ID.UnmarshalText([]byte(confJSON.ID)); err != nil {
            return err
        }
    }

    if confJSON.PrivateKey != "" {
        key, err := hex.DecodeString(confJSON.PrivateKey)
        if err != nil {
            return err
        }
        privKey, err := crypto.ToECDSA(key)
        if err != nil {
            return err
        }
        n.PrivateKey = privKey
    }

    n.Name = confJSON.Name
    n.Services = confJSON.Services
    n.Port = confJSON.Port
    n.EnableMsgEvents = confJSON.EnableMsgEvents

    return nil
}

// Node returns the node descriptor represented by the config.
func (n *NodeConfig) Node() *enode.Node {
    return enode.NewV4(&n.PrivateKey.PublicKey, net.IP{127, 0, 0, 1}, int(n.Port), int(n.Port))
}

// RandomNodeConfig returns node configuration with a randomly generated ID and
// PrivateKey
func RandomNodeConfig() *NodeConfig {
    key, err := crypto.GenerateKey()
    if err != nil {
        panic("unable to generate key")
    }

    id := enode.PubkeyToIDV4(&key.PublicKey)
    port, err := assignTCPPort()
    if err != nil {
        panic("unable to assign tcp port")
    }
    return &NodeConfig{
        ID:              id,
        Name:            fmt.Sprintf("node_%s", id.String()),
        PrivateKey:      key,
        Port:            port,
        EnableMsgEvents: true,
    }
}

func assignTCPPort() (uint16, error) {
    l, err := net.Listen("tcp", "127.0.0.1:0")
    if err != nil {
        return 0, err
    }
    l.Close()
    _, port, err := net.SplitHostPort(l.Addr().String())
    if err != nil {
        return 0, err
    }
    p, err := strconv.ParseInt(port, 10, 32)
    if err != nil {
        return 0, err
    }
    return uint16(p), nil
}

// ServiceContext is a collection of options and methods which can be utilised
// when starting services
type ServiceContext struct {
    RPCDialer

    NodeContext *node.ServiceContext
    Config      *NodeConfig
    Snapshot    []byte
}

// RPCDialer is used when initialising services which need to connect to
// other nodes in the network (for example a simulated Swarm node which needs
// to connect to a Geth node to resolve ENS names)
type RPCDialer interface {
    DialRPC(id enode.ID) (*rpc.Client, error)
}

// Services is a collection of services which can be run in a simulation
type Services map[string]ServiceFunc

// ServiceFunc returns a node.Service which can be used to boot a devp2p node
type ServiceFunc func(ctx *ServiceContext) (node.Service, error)

// serviceFuncs is a map of registered services which are used to boot devp2p
// nodes
var serviceFuncs = make(Services)

// RegisterServices registers the given Services which can then be used to
// start devp2p nodes using either the Exec or Docker adapters.
//
// It should be called in an init function so that it has the opportunity to
// execute the services before main() is called.
func RegisterServices(services Services) {
    for name, f := range services {
        if _, exists := serviceFuncs[name]; exists {
            panic(fmt.Sprintf("node service already exists: %q", name))
        }
        serviceFuncs[name] = f
    }

    // now we have registered the services, run reexec.Init() which will
    // potentially start one of the services if the current binary has
    // been exec'd with argv[0] set to "p2p-node"
    if reexec.Init() {
        os.Exit(0)
    }
}