aboutsummaryrefslogblamecommitdiffstats
path: root/p2p/simulations/adapters/types.go
blob: 6681726e40b621c70c9b87d43b80b5bbdda1628c (plain) (tree)
























                                                                                  
                 




                                                
                                                   















































                                                                          
                   




                                                                           


                                      







                                                                             

                                                            
                                        

                   




                                                                             





                                                           





                                                                             




                                                   















                                                                                        
                                                                               

                                  















                                                                 

                                                    



                  




                                                                                                   






                                                                               
 
                                                



                                                  
                           




















                                                                     
         
                             















                                                                              
                                                 































                                                                                   
// 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

    // 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)
    }
}