github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/p2p/simulations/adapters/types.go (about)

     1  // Copyright 2017 The Spectrum Authors
     2  // This file is part of the Spectrum library.
     3  //
     4  // The Spectrum 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 Spectrum 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 Spectrum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package adapters
    18  
    19  import (
    20  	"crypto/ecdsa"
    21  	"encoding/hex"
    22  	"encoding/json"
    23  	"fmt"
    24  	"net"
    25  	"os"
    26  
    27  	"github.com/SmartMeshFoundation/Spectrum/crypto"
    28  	"github.com/SmartMeshFoundation/Spectrum/node"
    29  	"github.com/SmartMeshFoundation/Spectrum/p2p"
    30  	"github.com/SmartMeshFoundation/Spectrum/p2p/discover"
    31  	"github.com/SmartMeshFoundation/Spectrum/rpc"
    32  	"github.com/docker/docker/pkg/reexec"
    33  )
    34  
    35  // Node represents a node in a simulation network which is created by a
    36  // NodeAdapter, for example:
    37  //
    38  // * SimNode    - An in-memory node
    39  // * ExecNode   - A child process node
    40  // * DockerNode - A Docker container node
    41  //
    42  type Node interface {
    43  	// Addr returns the node's address (e.g. an Enode URL)
    44  	Addr() []byte
    45  
    46  	// Client returns the RPC client which is created once the node is
    47  	// up and running
    48  	Client() (*rpc.Client, error)
    49  
    50  	// ServeRPC serves RPC requests over the given connection
    51  	ServeRPC(net.Conn) error
    52  
    53  	// Start starts the node with the given snapshots
    54  	Start(snapshots map[string][]byte) error
    55  
    56  	// Stop stops the node
    57  	Stop() error
    58  
    59  	// NodeInfo returns information about the node
    60  	NodeInfo() *p2p.NodeInfo
    61  
    62  	// Snapshots creates snapshots of the running services
    63  	Snapshots() (map[string][]byte, error)
    64  }
    65  
    66  // NodeAdapter is used to create Nodes in a simulation network
    67  type NodeAdapter interface {
    68  	// Name returns the name of the adapter for logging purposes
    69  	Name() string
    70  
    71  	// NewNode creates a new node with the given configuration
    72  	NewNode(config *NodeConfig) (Node, error)
    73  }
    74  
    75  // NodeConfig is the configuration used to start a node in a simulation
    76  // network
    77  type NodeConfig struct {
    78  	// ID is the node's ID which is used to identify the node in the
    79  	// simulation network
    80  	ID discover.NodeID
    81  
    82  	// PrivateKey is the node's private key which is used by the devp2p
    83  	// stack to encrypt communications
    84  	PrivateKey *ecdsa.PrivateKey
    85  
    86  	// Enable peer events for Msgs
    87  	EnableMsgEvents bool
    88  
    89  	// Name is a human friendly name for the node like "node01"
    90  	Name string
    91  
    92  	// Services are the names of the services which should be run when
    93  	// starting the node (for SimNodes it should be the names of services
    94  	// contained in SimAdapter.services, for other nodes it should be
    95  	// services registered by calling the RegisterService function)
    96  	Services []string
    97  
    98  	// function to sanction or prevent suggesting a peer
    99  	Reachable func(id discover.NodeID) bool
   100  }
   101  
   102  // nodeConfigJSON is used to encode and decode NodeConfig as JSON by encoding
   103  // all fields as strings
   104  type nodeConfigJSON struct {
   105  	ID         string   `json:"id"`
   106  	PrivateKey string   `json:"private_key"`
   107  	Name       string   `json:"name"`
   108  	Services   []string `json:"services"`
   109  }
   110  
   111  // MarshalJSON implements the json.Marshaler interface by encoding the config
   112  // fields as strings
   113  func (n *NodeConfig) MarshalJSON() ([]byte, error) {
   114  	confJSON := nodeConfigJSON{
   115  		ID:       n.ID.String(),
   116  		Name:     n.Name,
   117  		Services: n.Services,
   118  	}
   119  	if n.PrivateKey != nil {
   120  		confJSON.PrivateKey = hex.EncodeToString(crypto.FromECDSA(n.PrivateKey))
   121  	}
   122  	return json.Marshal(confJSON)
   123  }
   124  
   125  // UnmarshalJSON implements the json.Unmarshaler interface by decoding the json
   126  // string values into the config fields
   127  func (n *NodeConfig) UnmarshalJSON(data []byte) error {
   128  	var confJSON nodeConfigJSON
   129  	if err := json.Unmarshal(data, &confJSON); err != nil {
   130  		return err
   131  	}
   132  
   133  	if confJSON.ID != "" {
   134  		nodeID, err := discover.HexID(confJSON.ID)
   135  		if err != nil {
   136  			return err
   137  		}
   138  		n.ID = nodeID
   139  	}
   140  
   141  	if confJSON.PrivateKey != "" {
   142  		key, err := hex.DecodeString(confJSON.PrivateKey)
   143  		if err != nil {
   144  			return err
   145  		}
   146  		privKey, err := crypto.ToECDSA(key)
   147  		if err != nil {
   148  			return err
   149  		}
   150  		n.PrivateKey = privKey
   151  	}
   152  
   153  	n.Name = confJSON.Name
   154  	n.Services = confJSON.Services
   155  
   156  	return nil
   157  }
   158  
   159  // RandomNodeConfig returns node configuration with a randomly generated ID and
   160  // PrivateKey
   161  func RandomNodeConfig() *NodeConfig {
   162  	key, err := crypto.GenerateKey()
   163  	if err != nil {
   164  		panic("unable to generate key")
   165  	}
   166  	var id discover.NodeID
   167  	pubkey := crypto.FromECDSAPub(&key.PublicKey)
   168  	copy(id[:], pubkey[1:])
   169  	return &NodeConfig{
   170  		ID:         id,
   171  		PrivateKey: key,
   172  	}
   173  }
   174  
   175  // ServiceContext is a collection of options and methods which can be utilised
   176  // when starting services
   177  type ServiceContext struct {
   178  	RPCDialer
   179  
   180  	NodeContext *node.ServiceContext
   181  	Config      *NodeConfig
   182  	Snapshot    []byte
   183  }
   184  
   185  // RPCDialer is used when initialising services which need to connect to
   186  // other nodes in the network (for example a simulated Swarm node which needs
   187  // to connect to a Geth node to resolve ENS names)
   188  type RPCDialer interface {
   189  	DialRPC(id discover.NodeID) (*rpc.Client, error)
   190  }
   191  
   192  // Services is a collection of services which can be run in a simulation
   193  type Services map[string]ServiceFunc
   194  
   195  // ServiceFunc returns a node.Service which can be used to boot a devp2p node
   196  type ServiceFunc func(ctx *ServiceContext) (node.Service, error)
   197  
   198  // serviceFuncs is a map of registered services which are used to boot devp2p
   199  // nodes
   200  var serviceFuncs = make(Services)
   201  
   202  // RegisterServices registers the given Services which can then be used to
   203  // start devp2p nodes using either the Exec or Docker adapters.
   204  //
   205  // It should be called in an init function so that it has the opportunity to
   206  // execute the services before main() is called.
   207  func RegisterServices(services Services) {
   208  	for name, f := range services {
   209  		if _, exists := serviceFuncs[name]; exists {
   210  			panic(fmt.Sprintf("node service already exists: %q", name))
   211  		}
   212  		serviceFuncs[name] = f
   213  	}
   214  
   215  	// now we have registered the services, run reexec.Init() which will
   216  	// potentially start one of the services if the current binary has
   217  	// been exec'd with argv[0] set to "p2p-node"
   218  	if reexec.Init() {
   219  		os.Exit(0)
   220  	}
   221  }