p2p/simulations: Get peer events via RPC

Signed-off-by: Lewis Marshall <lewis@lmars.net>
This commit is contained in:
Lewis Marshall 2017-04-28 19:11:11 +01:00
parent a4b52df32c
commit 247e40a36a
14 changed files with 378 additions and 137 deletions

View file

@ -1,6 +1,7 @@
package adapters package adapters
import ( import (
"crypto/ecdsa"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -22,7 +23,7 @@ type DockerNode struct {
// NewDockerNode creates a new DockerNode, building the docker image if // NewDockerNode creates a new DockerNode, building the docker image if
// necessary // necessary
func NewDockerNode(id *NodeId, service string) (*DockerNode, error) { func NewDockerNode(id *NodeId, key *ecdsa.PrivateKey, service string) (*DockerNode, error) {
if runtime.GOOS != "linux" { if runtime.GOOS != "linux" {
return nil, fmt.Errorf("NewDockerNode can only be used on Linux as it uses the current binary (which must be a Linux binary)") return nil, fmt.Errorf("NewDockerNode can only be used on Linux as it uses the current binary (which must be a Linux binary)")
} }
@ -51,6 +52,7 @@ func NewDockerNode(id *NodeId, service string) (*DockerNode, error) {
ID: id, ID: id,
Service: service, Service: service,
Config: &conf, Config: &conf,
key: key,
}, },
} }
node.newCmd = node.dockerCommand node.newCmd = node.dockerCommand
@ -60,13 +62,13 @@ func NewDockerNode(id *NodeId, service string) (*DockerNode, error) {
// dockerCommand returns a command which exec's the binary in a docker // dockerCommand returns a command which exec's the binary in a docker
// container. // container.
// //
// It uses a shell so that we can pass the _P2P_NODE_CONFIG environment // It uses a shell so that we can pass the _P2P_NODE_CONFIG and _P2P_NODE_KEY
// variable to the container using the --env flag. // environment variables to the container using the --env flag.
func (n *DockerNode) dockerCommand() *exec.Cmd { func (n *DockerNode) dockerCommand() *exec.Cmd {
return exec.Command( return exec.Command(
"sh", "-c", "sh", "-c",
fmt.Sprintf( fmt.Sprintf(
`exec docker run --interactive --env _P2P_NODE_CONFIG="${_P2P_NODE_CONFIG}" %s p2p-node %s %s`, `exec docker run --interactive --env _P2P_NODE_CONFIG="${_P2P_NODE_CONFIG}" --env _P2P_NODE_KEY="${_P2P_NODE_KEY}" %s p2p-node %s %s`,
dockerImage, n.Service, n.ID.String(), dockerImage, n.Service, n.ID.String(),
), ),
) )

View file

@ -2,6 +2,8 @@ package adapters
import ( import (
"context" "context"
"crypto/ecdsa"
"encoding/hex"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -16,6 +18,7 @@ import (
"time" "time"
"github.com/docker/docker/pkg/reexec" "github.com/docker/docker/pkg/reexec"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
@ -55,11 +58,12 @@ type ExecNode struct {
client *rpc.Client client *rpc.Client
newCmd func() *exec.Cmd newCmd func() *exec.Cmd
key *ecdsa.PrivateKey
} }
// NewExecNode creates a new ExecNode which will run the given service using a // NewExecNode creates a new ExecNode which will run the given service using a
// sub-directory of the given baseDir // sub-directory of the given baseDir
func NewExecNode(id *NodeId, service, baseDir string) (*ExecNode, error) { func NewExecNode(id *NodeId, key *ecdsa.PrivateKey, service, baseDir string) (*ExecNode, error) {
if _, exists := serviceFuncs[service]; !exists { if _, exists := serviceFuncs[service]; !exists {
return nil, fmt.Errorf("unknown node service %q", service) return nil, fmt.Errorf("unknown node service %q", service)
} }
@ -82,6 +86,7 @@ func NewExecNode(id *NodeId, service, baseDir string) (*ExecNode, error) {
Service: service, Service: service,
Dir: dir, Dir: dir,
Config: &conf, Config: &conf,
key: key,
} }
node.newCmd = node.execCommand node.newCmd = node.execCommand
return node, nil return node, nil
@ -99,9 +104,10 @@ func (n *ExecNode) Client() (*rpc.Client, error) {
return n.client, nil return n.client, nil
} }
// Start exec's the node passing the ID and service as command line arguments // Start exec's the node passing the ID and service as command line arguments,
// and the node config encoded as JSON in the _P2P_NODE_CONFIG environment // the node config encoded as JSON in the _P2P_NODE_CONFIG environment
// variable // variable and the node's private key hex-endoded in the _P2P_NODE_KEY
// environment variable
func (n *ExecNode) Start() (err error) { func (n *ExecNode) Start() (err error) {
if n.Cmd != nil { if n.Cmd != nil {
return errors.New("already started") return errors.New("already started")
@ -119,6 +125,9 @@ func (n *ExecNode) Start() (err error) {
return fmt.Errorf("error generating node config: %s", err) return fmt.Errorf("error generating node config: %s", err)
} }
// encode the private key
key := hex.EncodeToString(crypto.FromECDSA(n.key))
// create a net.Pipe for RPC communication over stdin / stdout // create a net.Pipe for RPC communication over stdin / stdout
pipe1, pipe2 := net.Pipe() pipe1, pipe2 := net.Pipe()
@ -127,7 +136,10 @@ func (n *ExecNode) Start() (err error) {
cmd.Stdin = pipe1 cmd.Stdin = pipe1
cmd.Stdout = pipe1 cmd.Stdout = pipe1
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Env = append(os.Environ(), fmt.Sprintf("_P2P_NODE_CONFIG=%s", conf)) cmd.Env = append(os.Environ(),
fmt.Sprintf("_P2P_NODE_CONFIG=%s", conf),
fmt.Sprintf("_P2P_NODE_KEY=%s", key),
)
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
return fmt.Errorf("error starting node: %s", err) return fmt.Errorf("error starting node: %s", err)
} }
@ -215,6 +227,17 @@ func execP2PNode() {
log.Crit("error decoding _P2P_NODE_CONFIG", "err", err) log.Crit("error decoding _P2P_NODE_CONFIG", "err", err)
} }
// decode the private key
keyEnv := os.Getenv("_P2P_NODE_KEY")
if keyEnv == "" {
log.Crit("missing _P2P_NODE_KEY")
}
key, err := hex.DecodeString(keyEnv)
if err != nil {
log.Crit("error decoding _P2P_NODE_KEY", "err", err)
}
conf.P2P.PrivateKey = crypto.ToECDSA(key)
// initialize the service // initialize the service
serviceFunc, exists := serviceFuncs[serviceName] serviceFunc, exists := serviceFuncs[serviceName]
if !exists { if !exists {
@ -268,10 +291,22 @@ func startP2PNode(conf *node.Config, service node.Service) (*node.Node, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
constructor := func(ctx *node.ServiceContext) (node.Service, error) {
return service, nil constructor := func(s node.Service) node.ServiceConstructor {
return func(ctx *node.ServiceContext) (node.Service, error) {
return s, nil
} }
if err := stack.Register(constructor); err != nil { }
// register the peer events API
//
// TODO: move this to node.PrivateAdminAPI once the following is merged:
// https://github.com/ethereum/go-ethereum/pull/13885
if err := stack.Register(constructor(&PeerAPI{stack.Server})); err != nil {
return nil, err
}
if err := stack.Register(constructor(service)); err != nil {
return nil, err return nil, err
} }
if err := stack.Start(); err != nil { if err := stack.Start(); err != nil {
@ -280,6 +315,65 @@ func startP2PNode(conf *node.Config, service node.Service) (*node.Node, error) {
return stack, nil return stack, nil
} }
// PeerAPI is used to expose peer events under the "eth" RPC namespace.
//
// TODO: move this to node.PrivateAdminAPI and expose under the "admin"
// namespace once the following is merged:
// https://github.com/ethereum/go-ethereum/pull/13885
type PeerAPI struct {
server func() p2p.Server
}
func (p *PeerAPI) Protocols() []p2p.Protocol {
return nil
}
func (p *PeerAPI) APIs() []rpc.API {
return []rpc.API{{
Namespace: "eth",
Version: "1.0",
Service: p,
}}
}
func (p *PeerAPI) Start(p2p.Server) error {
return nil
}
func (p *PeerAPI) Stop() error {
return nil
}
// PeerEvents creates an RPC sunscription which receives peer events from the
// underlying p2p.Server
func (p *PeerAPI) PeerEvents(ctx context.Context) (*rpc.Subscription, error) {
notifier, supported := rpc.NotifierFromContext(ctx)
if !supported {
return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
}
rpcSub := notifier.CreateSubscription()
go func() {
events := make(chan *p2p.PeerEvent)
sub := p.server().SubscribePeers(events)
defer sub.Unsubscribe()
for {
select {
case event := <-events:
notifier.Notify(rpcSub.ID, event)
case <-rpcSub.Err():
return
case <-notifier.Closed():
return
}
}
}()
return rpcSub, nil
}
// stdioConn wraps os.Stdin / os.Stdout with a nop Close method so we can // stdioConn wraps os.Stdin / os.Stdout with a nop Close method so we can
// use them to handle RPC messages // use them to handle RPC messages
type stdioConn struct { type stdioConn struct {

View file

@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"sync" "sync"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
@ -59,6 +60,7 @@ type SimNode struct {
service node.Service service node.Service
peerMap map[discover.NodeID]int peerMap map[discover.NodeID]int
peers []*Peer peers []*Peer
peerFeed event.Feed
client *rpc.Client client *rpc.Client
} }
@ -115,13 +117,20 @@ func (self *SimNode) startRPC() error {
return errors.New("RPC already started") return errors.New("RPC already started")
} }
// add SimAdminAPI so that the network can call the AddPeer // add SimAdminAPI and PeerAPI so that the network can call the
// and RemovePeer RPC methods // AddPeer, RemovePeer and PeerEvents RPC methods
apis := append(self.service.APIs(), rpc.API{ apis := append(self.service.APIs(), []rpc.API{
{
Namespace: "admin", Namespace: "admin",
Version: "1.0", Version: "1.0",
Service: &SimAdminAPI{self}, Service: &SimAdminAPI{self},
}) },
{
Namespace: "eth",
Version: "1.0",
Service: &PeerAPI{func() p2p.Server { return self }},
},
}...)
// start the RPC handler // start the RPC handler
handler := rpc.NewServer() handler := rpc.NewServer()
@ -219,6 +228,10 @@ func (self *SimNode) AddPeer(node *discover.Node) {
self.RunProtocol(na.(*SimNode), rw, rrw, peer) self.RunProtocol(na.(*SimNode), rw, rrw, peer)
} }
func (self *SimNode) SubscribePeers(ch chan *p2p.PeerEvent) event.Subscription {
return self.peerFeed.Subscribe(ch)
}
func (self *SimNode) PeerCount() int { func (self *SimNode) PeerCount() int {
self.lock.Lock() self.lock.Lock()
defer self.lock.Unlock() defer self.lock.Unlock()
@ -243,13 +256,13 @@ func (self *SimNode) RunProtocol(node *SimNode, rw, rrw p2p.MsgReadWriter, peer
log.Trace(fmt.Sprintf("protocol starting on peer %v (connection with %v)", self.Id, id)) log.Trace(fmt.Sprintf("protocol starting on peer %v (connection with %v)", self.Id, id))
p := p2p.NewPeer(id.NodeID, id.Label(), []p2p.Cap{}) p := p2p.NewPeer(id.NodeID, id.Label(), []p2p.Cap{})
go func() { go func() {
self.network.DidConnect(self.Id, id) self.peerFeed.Send(&p2p.PeerEvent{Type: p2p.PeerEventTypeAdd, Peer: id.NodeID})
err := protocol.Run(p, rw) err := protocol.Run(p, rw)
<-peer.Readyc <-peer.Readyc
self.RemovePeer(node.Node()) self.RemovePeer(node.Node())
peer.Errc <- err peer.Errc <- err
log.Trace(fmt.Sprintf("protocol quit on peer %v (connection with %v broken: %v)", self.Id, id, err)) log.Trace(fmt.Sprintf("protocol quit on peer %v (connection with %v broken: %v)", self.Id, id, err))
self.network.DidDisconnect(self.Id, id) self.peerFeed.Send(&p2p.PeerEvent{Type: p2p.PeerEventTypeDrop, Peer: id.NodeID})
}() }()
} }

View file

@ -17,7 +17,6 @@
package adapters package adapters
import ( import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
@ -79,22 +78,3 @@ type Reporter interface {
DidConnect(*NodeId, *NodeId) error DidConnect(*NodeId, *NodeId) error
DidDisconnect(*NodeId, *NodeId) error DidDisconnect(*NodeId, *NodeId) error
} }
func RandomNodeId() *NodeId {
key, err := crypto.GenerateKey()
if err != nil {
panic("unable to generate key")
}
var id discover.NodeID
pubkey := crypto.FromECDSAPub(&key.PublicKey)
copy(id[:], pubkey[1:])
return &NodeId{id}
}
func RandomNodeIds(n int) []*NodeId {
var ids []*NodeId
for i := 0; i < n; i++ {
ids = append(ids, RandomNodeId())
}
return ids
}

View file

@ -60,6 +60,26 @@ type protoHandshake struct {
Rest []rlp.RawValue `rlp:"tail"` Rest []rlp.RawValue `rlp:"tail"`
} }
// PeerEventType is the type of peer events emitted by a p2p.Server
type PeerEventType string
const (
// PeerEventTypeAdd is the type of event emitted when a peer is added
// to a p2p.Server
PeerEventTypeAdd PeerEventType = "add"
// PeerEventTypeDrop is the type of event emitted when a peer is
// dropped from a p2p.Server
PeerEventTypeDrop PeerEventType = "drop"
)
// PeerEvent is an event emitted when peers are either added or dropped from
// a p2p.Server
type PeerEvent struct {
Type PeerEventType
Peer discover.NodeID
}
// Peer represents a connected remote node. // Peer represents a connected remote node.
type Peer struct { type Peer struct {
rw *conn rw *conn

View file

@ -9,6 +9,7 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/adapters" "github.com/ethereum/go-ethereum/p2p/adapters"
"github.com/ethereum/go-ethereum/p2p/simulations"
p2ptest "github.com/ethereum/go-ethereum/p2p/testing" p2ptest "github.com/ethereum/go-ethereum/p2p/testing"
) )
@ -117,8 +118,8 @@ func newProtocol(pp *p2ptest.TestPeerPool) adapters.ProtoCall {
} }
func protocolTester(t *testing.T, pp *p2ptest.TestPeerPool) *p2ptest.ProtocolTester { func protocolTester(t *testing.T, pp *p2ptest.TestPeerPool) *p2ptest.ProtocolTester {
id := adapters.RandomNodeId() conf := simulations.RandomNodeConfig()
return p2ptest.NewProtocolTester(t, id, 2, newProtocol(pp)) return p2ptest.NewProtocolTester(t, conf.Id, 2, newProtocol(pp))
} }
func protoHandshakeExchange(id *adapters.NodeId, proto *protoHandshake) []p2ptest.Exchange { func protoHandshakeExchange(id *adapters.NodeId, proto *protoHandshake) []p2ptest.Exchange {

View file

@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/discv5" "github.com/ethereum/go-ethereum/p2p/discv5"
@ -141,6 +142,7 @@ type Server interface {
Stop() error Stop() error
AddPeer(node *discover.Node) AddPeer(node *discover.Node)
RemovePeer(node *discover.Node) RemovePeer(node *discover.Node)
SubscribePeers(ch chan *PeerEvent) event.Subscription
PeerCount() int PeerCount() int
NodeInfo() *NodeInfo NodeInfo() *NodeInfo
PeersInfo() []*PeerInfo PeersInfo() []*PeerInfo
@ -180,6 +182,7 @@ type server struct {
addpeer chan *conn addpeer chan *conn
delpeer chan peerDrop delpeer chan peerDrop
loopWG sync.WaitGroup // loop, listenLoop loopWG sync.WaitGroup // loop, listenLoop
peerFeed event.Feed
} }
type peerOpFunc func(map[discover.NodeID]*Peer) type peerOpFunc func(map[discover.NodeID]*Peer)
@ -305,6 +308,11 @@ func (srv *server) RemovePeer(node *discover.Node) {
} }
} }
// SubscribePeers subscribes the given channel to peer events
func (srv *server) SubscribePeers(ch chan *PeerEvent) event.Subscription {
return srv.peerFeed.Subscribe(ch)
}
// Self returns the local node's endpoint information. // Self returns the local node's endpoint information.
func (srv *server) Self() *discover.Node { func (srv *server) Self() *discover.Node {
srv.lock.Lock() srv.lock.Lock()
@ -770,6 +778,11 @@ func (srv *server) runPeer(p *Peer) {
if srv.newPeerHook != nil { if srv.newPeerHook != nil {
srv.newPeerHook(p) srv.newPeerHook(p)
} }
// broadcast peer add / drop events
srv.peerFeed.Send(&PeerEvent{Type: PeerEventTypeAdd, Peer: p.ID()})
defer srv.peerFeed.Send(&PeerEvent{Type: PeerEventTypeDrop, Peer: p.ID()})
remoteRequested, err := p.run() remoteRequested, err := p.run()
// Note: run waits for existing peers to be sent on srv.delpeer // Note: run waits for existing peers to be sent on srv.delpeer
// before returning, so this send should not select on srv.quit. // before returning, so this send should not select on srv.quit.

View file

@ -163,7 +163,8 @@ func MockEvents(eventer *event.TypeMux, ids []*adapters.NodeId, conf *MockerConf
var mustconnect []int var mustconnect []int
for i := 0; len(offNodes) > 0 && i < nodesUp; i++ { for i := 0; len(offNodes) > 0 && i < nodesUp; i++ {
c := rand.Intn(len(offNodes)) c := rand.Intn(len(offNodes))
sn := &Node{Id: offNodes[c]} sn := &Node{}
sn.Id = offNodes[c]
err := eventer.Post(sn.EmitEvent(ControlEvent)) err := eventer.Post(sn.EmitEvent(ControlEvent))
if err != nil { if err != nil {
panic(err.Error()) panic(err.Error())

View file

@ -27,14 +27,19 @@
package simulations package simulations
import ( import (
"context"
"crypto/ecdsa"
"encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"reflect" "reflect"
"sync" "sync"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/adapters" "github.com/ethereum/go-ethereum/p2p/adapters"
"github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
@ -176,9 +181,7 @@ func NewNodesController(net *Network) *ResourceController {
&ResourceHandlers{ &ResourceHandlers{
Create: &ResourceHandler{ Create: &ResourceHandler{
Handle: func(msg interface{}, parent *ResourceController) (interface{}, error) { Handle: func(msg interface{}, parent *ResourceController) (interface{}, error) {
nodeid := adapters.RandomNodeId() return net.NewNode()
net.NewNode(&NodeConfig{Id: nodeid})
return &NodeConfig{Id: nodeid}, nil
}, },
}, },
Retrieve: &ResourceHandler{ Retrieve: &ResourceHandler{
@ -289,7 +292,7 @@ func (self *Network) Subscribe(eventer *event.TypeMux, types ...interface{}) {
func (self *Network) executeNodeEvent(ne *NodeControlEvent) { func (self *Network) executeNodeEvent(ne *NodeControlEvent) {
if ne.Up { if ne.Up {
err := self.NewNode(&NodeConfig{Id: ne.Node.Id}) err := self.NewNodeWithConfig(&ne.Node.NodeConfig)
if err != nil { if err != nil {
log.Trace(fmt.Sprintf("error execute event %v: %v", ne, err)) log.Trace(fmt.Sprintf("error execute event %v: %v", ne, err))
} }
@ -370,9 +373,10 @@ type LiveEventer interface {
} }
type Node struct { type Node struct {
Id *adapters.NodeId `json:"id"` NodeConfig
Up bool Up bool
config *NodeConfig
na adapters.NodeAdapter na adapters.NodeAdapter
controlFired bool controlFired bool
} }
@ -511,7 +515,55 @@ func (self *Node) Adapter() adapters.NodeAdapter {
} }
type NodeConfig struct { type NodeConfig struct {
Id *adapters.NodeId `json:"Id"` Id *adapters.NodeId
PrivateKey *ecdsa.PrivateKey
}
type nodeConfigJSON struct {
Id string `json:"id"`
PrivateKey string `json:"private_key"`
}
func (n *NodeConfig) MarshalJSON() ([]byte, error) {
return json.Marshal(nodeConfigJSON{
n.Id.String(),
hex.EncodeToString(crypto.FromECDSA(n.PrivateKey)),
})
}
func (n *NodeConfig) UnmarshalJSON(data []byte) error {
var confJSON nodeConfigJSON
if err := json.Unmarshal(data, &confJSON); err != nil {
return err
}
nodeID, err := discover.HexID(confJSON.Id)
if err != nil {
return err
}
n.Id = &adapters.NodeId{NodeID: nodeID}
key, err := hex.DecodeString(confJSON.PrivateKey)
if err != nil {
return err
}
n.PrivateKey = crypto.ToECDSA(key)
return nil
}
func RandomNodeConfig() *NodeConfig {
key, err := crypto.GenerateKey()
if err != nil {
panic("unable to generate key")
}
var id discover.NodeID
pubkey := crypto.FromECDSAPub(&key.PublicKey)
copy(id[:], pubkey[1:])
return &NodeConfig{
Id: &adapters.NodeId{NodeID: id},
PrivateKey: key,
}
} }
// TODO: ignored for now // TODO: ignored for now
@ -530,9 +582,18 @@ type Know struct {
// swap balance // swap balance
} }
// NewNode adds a new node to the network // NewNode adds a new node to the network with a random ID
func (self *Network) NewNode() (*NodeConfig, error) {
conf := RandomNodeConfig()
if err := self.NewNodeWithConfig(conf); err != nil {
return nil, err
}
return conf, nil
}
// NewNodeWithConfig adds a new node to the network with the given config
// errors if a node by the same id already exist // errors if a node by the same id already exist
func (self *Network) NewNode(conf *NodeConfig) error { func (self *Network) NewNodeWithConfig(conf *NodeConfig) error {
self.lock.Lock() self.lock.Lock()
defer self.lock.Unlock() defer self.lock.Unlock()
id := conf.Id id := conf.Id
@ -544,8 +605,7 @@ func (self *Network) NewNode(conf *NodeConfig) error {
self.nodeMap[id.NodeID] = len(self.Nodes) self.nodeMap[id.NodeID] = len(self.Nodes)
na := self.naf(conf) na := self.naf(conf)
node := &Node{ node := &Node{
Id: conf.Id, NodeConfig: *conf,
config: conf,
na: na, na: na,
} }
self.Nodes = append(self.Nodes, node) self.Nodes = append(self.Nodes, node)
@ -603,9 +663,49 @@ func (self *Network) Start(id *adapters.NodeId) error {
log.Info(fmt.Sprintf("started node %v: %v", id, node.Up)) log.Info(fmt.Sprintf("started node %v: %v", id, node.Up))
self.events.Post(node.EmitEvent(ControlEvent)) self.events.Post(node.EmitEvent(ControlEvent))
// subscribe to peer events
client, err := node.Adapter().Client()
if err != nil {
return fmt.Errorf("error getting rpc client for node %v: %s", id, err)
}
events := make(chan *p2p.PeerEvent)
sub, err := client.EthSubscribe(context.Background(), events, "peerEvents")
if err != nil {
return fmt.Errorf("error getting peer events for node %v: %s", id, err)
}
go self.watchPeerEvents(id, events, sub)
return nil return nil
} }
func (self *Network) watchPeerEvents(id *adapters.NodeId, events chan *p2p.PeerEvent, sub event.Subscription) {
defer sub.Unsubscribe()
for {
select {
case event, ok := <-events:
if !ok {
return
}
peer := &adapters.NodeId{NodeID: event.Peer}
switch event.Type {
case p2p.PeerEventTypeAdd:
if err := self.DidConnect(id, peer); err != nil {
log.Error(fmt.Sprintf("error generating connection up event %s => %s", id.Label(), peer.Label()), "err", err)
}
case p2p.PeerEventTypeDrop:
if err := self.DidDisconnect(id, peer); err != nil {
log.Error(fmt.Sprintf("error generating connection down event %s => %s", id.Label(), peer.Label()), "err", err)
}
}
case err := <-sub.Err():
if err != nil {
log.Error(fmt.Sprintf("error getting peer events for node %v", id), "err", err)
}
return
}
}
}
// Stop(id) shuts down the node (relevant only for instance with own p2p or remote) // Stop(id) shuts down the node (relevant only for instance with own p2p or remote)
func (self *Network) Stop(id *adapters.NodeId) error { func (self *Network) Stop(id *adapters.NodeId) error {
node := self.GetNode(id) node := self.GetNode(id)
@ -629,6 +729,7 @@ func (self *Network) Stop(id *adapters.NodeId) error {
// calling the node's nodadapters Connect method // calling the node's nodadapters Connect method
// connection is established (as if) the first node dials out to the other // connection is established (as if) the first node dials out to the other
func (self *Network) Connect(oneId, otherId *adapters.NodeId) error { func (self *Network) Connect(oneId, otherId *adapters.NodeId) error {
log.Debug(fmt.Sprintf("connecting %s to %s", oneId, otherId))
conn, err := self.GetOrCreateConn(oneId, otherId) conn, err := self.GetOrCreateConn(oneId, otherId)
if err != nil { if err != nil {
return err return err
@ -808,6 +909,7 @@ func (self *Network) getConn(oneId, otherId *adapters.NodeId) *Conn {
func (self *Network) Shutdown() { func (self *Network) Shutdown() {
// disconnect all nodes // disconnect all nodes
for _, conn := range self.Conns { for _, conn := range self.Conns {
log.Debug(fmt.Sprintf("disconnecting %s from %s", conn.One.Label(), conn.Other.Label()))
if err := self.Disconnect(conn.One, conn.Other); err != nil { if err := self.Disconnect(conn.One, conn.Other); err != nil {
log.Warn(fmt.Sprintf("error disconnecting %s from %s", conn.One.Label(), conn.Other.Label()), "err", err) log.Warn(fmt.Sprintf("error disconnecting %s from %s", conn.One.Label(), conn.Other.Label()), "err", err)
} }
@ -815,6 +917,7 @@ func (self *Network) Shutdown() {
// stop all nodes // stop all nodes
for _, node := range self.Nodes { for _, node := range self.Nodes {
log.Debug(fmt.Sprintf("stopping node %s", node.Id.Label()))
if err := node.na.Stop(); err != nil { if err := node.na.Stop(); err != nil {
log.Warn(fmt.Sprintf("error stopping node %s", node.Id.Label()), "err", err) log.Warn(fmt.Sprintf("error stopping node %s", node.Id.Label()), "err", err)
} }

View file

@ -28,7 +28,7 @@ func NewProtocolTester(t *testing.T, id *adapters.NodeId, n int, run func(*p2p.P
} }
net.SetNaf(naf) net.SetNaf(naf)
if err := net.NewNode(&simulations.NodeConfig{Id: id}); err != nil { if err := net.NewNodeWithConfig(&simulations.NodeConfig{Id: id}); err != nil {
panic(err.Error()) panic(err.Error())
} }
if err := net.Start(id); err != nil { if err := net.Start(id); err != nil {
@ -36,30 +36,35 @@ func NewProtocolTester(t *testing.T, id *adapters.NodeId, n int, run func(*p2p.P
} }
node := net.GetNodeAdapter(id).(*adapters.SimNode) node := net.GetNodeAdapter(id).(*adapters.SimNode)
ids := adapters.RandomNodeIds(n) peers := make([]*simulations.NodeConfig, n)
ps := NewProtocolSession(node, ids) peerIDs := make([]*adapters.NodeId, n)
for i := 0; i < n; i++ {
peers[i] = simulations.RandomNodeConfig()
peerIDs[i] = peers[i].Id
}
ps := NewProtocolSession(node, peerIDs)
self := &ProtocolTester{ self := &ProtocolTester{
ProtocolSession: ps, ProtocolSession: ps,
network: net, network: net,
} }
self.Connect(id, ids...) self.Connect(id, peers...)
return self return self
} }
func (self *ProtocolTester) Connect(selfId *adapters.NodeId, ids ...*adapters.NodeId) { func (self *ProtocolTester) Connect(selfId *adapters.NodeId, peers ...*simulations.NodeConfig) {
for _, id := range ids { for _, peer := range peers {
log.Trace(fmt.Sprintf("start node %v", id)) log.Trace(fmt.Sprintf("start node %v", peer.Id))
if err := self.network.NewNode(&simulations.NodeConfig{Id: id}); err != nil { if err := self.network.NewNodeWithConfig(peer); err != nil {
panic(fmt.Sprintf("error starting peer %v: %v", id, err)) panic(fmt.Sprintf("error starting peer %v: %v", peer.Id, err))
} }
if err := self.network.Start(id); err != nil { if err := self.network.Start(peer.Id); err != nil {
panic(fmt.Sprintf("error starting peer %v: %v", id, err)) panic(fmt.Sprintf("error starting peer %v: %v", peer.Id, err))
} }
log.Trace(fmt.Sprintf("connect to %v", id)) log.Trace(fmt.Sprintf("connect to %v", peer.Id))
if err := self.network.Connect(selfId, id); err != nil { if err := self.network.Connect(selfId, peer.Id); err != nil {
panic(fmt.Sprintf("error connecting to peer %v: %v", id, err)) panic(fmt.Sprintf("error connecting to peer %v: %v", peer.Id, err))
} }
} }

View file

@ -82,7 +82,6 @@ func TestRegisterAndConnect(t *testing.T) {
}, },
ticker: make(chan time.Time), ticker: make(chan time.Time),
} }
//pp.Start(tc.connect, tc.ping)
pp.Start(s.TestNodeAdapter, tc.ping) pp.Start(s.TestNodeAdapter, tc.ping)
defer pp.Stop() defer pp.Stop()
tc.ticker <- time.Now() tc.ticker <- time.Now()

View file

@ -86,6 +86,7 @@ func (n *pssTestNode) triggerCheck() {
//go func() { n.trigger <- n.id }() //go func() { n.trigger <- n.id }()
go func() { n.trigger <- adapters.NewNodeId(n.Addr()) }() go func() { n.trigger <- adapters.NewNodeId(n.Addr()) }()
} }
/* /*
func (n *pssTestNode) RunProtocol(id *adapters.NodeId, rw, rrw p2p.MsgReadWriter, peer *adapters.Peer) error { func (n *pssTestNode) RunProtocol(id *adapters.NodeId, rw, rrw p2p.MsgReadWriter, peer *adapters.Peer) error {
return n.NodeAdapter.(adapters.ProtocolRunner).RunProtocol(id, rw, rrw, peer) return n.NodeAdapter.(adapters.ProtocolRunner).RunProtocol(id, rw, rrw, peer)
@ -686,11 +687,13 @@ func newPssSimulationTester(t *testing.T, numnodes int, numfullnodes int, net *s
//svcs[conf.Id] = testservice //svcs[conf.Id] = testservice
return node.SimNode return node.SimNode
}) })
ids := adapters.RandomNodeIds(numnodes) configs := make([]*simulations.NodeConfig, numnodes)
i := 0 for i := 0; i < numnodes; i++ {
for _, id := range ids { configs[i] = simulations.RandomNodeConfig()
addr := NewPeerAddrFromNodeId(id) }
psss[id] = makePss(addr.OverlayAddr()) for i, conf := range configs {
addr := NewPeerAddrFromNodeId(conf.Id)
psss[conf.Id] = makePss(addr.OverlayAddr())
if i < numfullnodes { if i < numfullnodes {
tp := &pssTestPeer{ tp := &pssTestPeer{
Peer: &protocols.Peer{ Peer: &protocols.Peer{
@ -699,17 +702,16 @@ func newPssSimulationTester(t *testing.T, numnodes int, numfullnodes int, net *s
successC: make(chan bool), successC: make(chan bool),
resultC: make(chan int), resultC: make(chan int),
} }
testpeers[id] = tp testpeers[conf.Id] = tp
targetprotocol := makeCustomProtocol(name, version, vct, testpeers[id]) targetprotocol := makeCustomProtocol(name, version, vct, testpeers[conf.Id])
pssprotocol := NewPssProtocol(psss[id], &topic, vct, targetprotocol) pssprotocol := NewPssProtocol(psss[conf.Id], &topic, vct, targetprotocol)
psss[id].Register(topic, pssprotocol.GetHandler()) psss[conf.Id].Register(topic, pssprotocol.GetHandler())
} }
net.NewNode(&simulations.NodeConfig{Id: id}) net.NewNodeWithConfig(conf)
if err := net.Start(id); err != nil { if err := net.Start(conf.Id); err != nil {
t.Fatalf("error starting node %s: %s", id.Label(), err) t.Fatalf("error starting node %s: %s", conf.Id.Label(), err)
} }
i++
} }
return nodes return nodes

View file

@ -54,7 +54,7 @@ func TestDiscoverySimulationDockerAdapter(t *testing.T) {
}) })
net.SetNaf(func(conf *simulations.NodeConfig) adapters.NodeAdapter { net.SetNaf(func(conf *simulations.NodeConfig) adapters.NodeAdapter {
node, err := adapters.NewDockerNode(conf.Id, serviceName) node, err := adapters.NewDockerNode(conf.Id, conf.PrivateKey, serviceName)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -84,7 +84,7 @@ func TestDiscoverySimulationExecAdapter(t *testing.T) {
}) })
net.SetNaf(func(conf *simulations.NodeConfig) adapters.NodeAdapter { net.SetNaf(func(conf *simulations.NodeConfig) adapters.NodeAdapter {
node, err := adapters.NewExecNode(conf.Id, serviceName, baseDir) node, err := adapters.NewExecNode(conf.Id, conf.PrivateKey, serviceName, baseDir)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -118,12 +118,16 @@ func testDiscoverySimulation(t *testing.T, setup func(net *simulations.Network,
}) })
defer net.Shutdown() defer net.Shutdown()
setup(net, trigger) setup(net, trigger)
ids := adapters.RandomNodeIds(nodeCount) ids := make([]*adapters.NodeId, nodeCount)
for _, id := range ids { for i := 0; i < nodeCount; i++ {
net.NewNode(&simulations.NodeConfig{Id: id}) conf, err := net.NewNode()
if err := net.Start(id); err != nil { if err != nil {
t.Fatalf("error starting node %s: %s", id.Label(), err) t.Fatalf("error starting node %s: %s", conf.Id.Label(), err)
} }
if err := net.Start(conf.Id); err != nil {
t.Fatalf("error starting node %s: %s", conf.Id.Label(), err)
}
ids[i] = conf.Id
} }
// run a simulation which connects the 10 nodes in a ring and waits // run a simulation which connects the 10 nodes in a ring and waits

View file

@ -118,11 +118,16 @@ func nethook(conf *simulations.NetworkConfig) (simulations.NetworkControl, *simu
conf.Backend = true conf.Backend = true
net := NewNetwork(simulations.NewNetwork(conf)) net := NewNetwork(simulations.NewNetwork(conf))
//ids := p2ptest.RandomNodeIds(10) ids := make([]*adapters.NodeId, 10)
ids := adapters.RandomNodeIds(10) for i := 0; i < 10; i++ {
conf, err := net.NewNode()
if err != nil {
panic(err.Error())
}
ids[i] = conf.Id
}
for i, id := range ids { for i, id := range ids {
net.NewNode(&simulations.NodeConfig{Id: id})
var peerId *adapters.NodeId var peerId *adapters.NodeId
if i == 0 { if i == 0 {
peerId = ids[len(ids)-1] peerId = ids[len(ids)-1]
@ -138,7 +143,6 @@ func nethook(conf *simulations.NetworkConfig) (simulations.NetworkControl, *simu
for _, id := range ids { for _, id := range ids {
n := rand.Intn(1000) n := rand.Intn(1000)
time.Sleep(time.Duration(n) * time.Millisecond) time.Sleep(time.Duration(n) * time.Millisecond)
net.NewNode(&simulations.NodeConfig{Id: id})
net.Start(id) net.Start(id)
log.Debug(fmt.Sprintf("node %v starting up", id)) log.Debug(fmt.Sprintf("node %v starting up", id))
// time.Sleep(1000 * time.Millisecond) // time.Sleep(1000 * time.Millisecond)