diff --git a/cmd/swarm/main.go b/cmd/swarm/main.go index aaf2e3ad28..e6da14c83e 100644 --- a/cmd/swarm/main.go +++ b/cmd/swarm/main.go @@ -459,7 +459,7 @@ func getPassPhrase(prompt string, i int, passwords []string) string { return password } -func injectBootnodes(srv p2p.Server, nodes []string) { +func injectBootnodes(srv *p2p.Server, nodes []string) { for _, url := range nodes { n, err := discover.ParseNode(url) if err != nil { diff --git a/cmd/wnode/main.go b/cmd/wnode/main.go index 5d5aa77ee8..f18025dff8 100644 --- a/cmd/wnode/main.go +++ b/cmd/wnode/main.go @@ -51,7 +51,7 @@ const quitCommand = "~Q" // singletons var ( - server p2p.Server + server *p2p.Server shh *whisper.Whisper done chan struct{} mailServer mailserver.WMailServer @@ -253,17 +253,19 @@ func initialize() { maxPeers = 800 } - server = p2p.NewServer(p2p.Config{ - PrivateKey: nodeid, - MaxPeers: maxPeers, - Name: common.MakeName("wnode", "5.0"), - Protocols: shh.Protocols(), - ListenAddr: *argIP, - NAT: nat.Any(), - BootstrapNodes: peers, - StaticNodes: peers, - TrustedNodes: peers, - }) + server = &p2p.Server{ + Config: p2p.Config{ + PrivateKey: nodeid, + MaxPeers: maxPeers, + Name: common.MakeName("wnode", "5.0"), + Protocols: shh.Protocols(), + ListenAddr: *argIP, + NAT: nat.Any(), + BootstrapNodes: peers, + StaticNodes: peers, + TrustedNodes: peers, + }, + } } func startServer() { diff --git a/contracts/release/release.go b/contracts/release/release.go index e0441fd41e..28a35381d4 100644 --- a/contracts/release/release.go +++ b/contracts/release/release.go @@ -94,7 +94,7 @@ func (r *ReleaseService) Protocols() []p2p.Protocol { return nil } func (r *ReleaseService) APIs() []rpc.API { return nil } // Start spawns the periodic version checker goroutine -func (r *ReleaseService) Start(server p2p.Server) error { +func (r *ReleaseService) Start(server *p2p.Server) error { go r.checker() return nil } diff --git a/eth/backend.go b/eth/backend.go index 9361cb3064..f864b1d88b 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -49,7 +49,7 @@ import ( ) type LesServer interface { - Start(srvr p2p.Server) + Start(srvr *p2p.Server) Stop() Protocols() []p2p.Protocol } @@ -362,7 +362,7 @@ func (s *Ethereum) Protocols() []p2p.Protocol { // Start implements node.Service, starting all internal goroutines needed by the // Ethereum protocol implementation. -func (s *Ethereum) Start(srvr p2p.Server) error { +func (s *Ethereum) Start(srvr *p2p.Server) error { s.netRPCService = ethapi.NewPublicNetAPI(srvr, s.NetVersion()) s.protocolManager.Start() diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index f295fb60d0..8765da8faf 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -52,7 +52,7 @@ const historyUpdateRange = 50 type Service struct { stack *node.Node // Temporary workaround, remove when API finalized - server p2p.Server // Peer-to-peer server to retrieve networking infos + server *p2p.Server // Peer-to-peer server to retrieve networking infos eth *eth.Ethereum // Full Ethereum service if monitoring a full node les *les.LightEthereum // Light Ethereum service if monitoring a light node engine consensus.Engine // Consensus engine to retrieve variadic block fields @@ -101,7 +101,7 @@ func (s *Service) Protocols() []p2p.Protocol { return nil } func (s *Service) APIs() []rpc.API { return nil } // Start implements node.Service, starting up the monitoring and reporting daemon. -func (s *Service) Start(server p2p.Server) error { +func (s *Service) Start(server *p2p.Server) error { s.server = server go s.loop() diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 146e7f0475..f9eed87975 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1434,12 +1434,12 @@ func (api *PrivateDebugAPI) SetHead(number hexutil.Uint64) { // PublicNetAPI offers network related RPC methods type PublicNetAPI struct { - net p2p.Server + net *p2p.Server networkVersion uint64 } // NewPublicNetAPI creates a new net API instance. -func NewPublicNetAPI(net p2p.Server, networkVersion uint64) *PublicNetAPI { +func NewPublicNetAPI(net *p2p.Server, networkVersion uint64) *PublicNetAPI { return &PublicNetAPI{net, networkVersion} } diff --git a/les/backend.go b/les/backend.go index 3dac7f15fc..646c81a7b1 100644 --- a/les/backend.go +++ b/les/backend.go @@ -185,7 +185,7 @@ func (s *LightEthereum) Protocols() []p2p.Protocol { // Start implements node.Service, starting all internal goroutines needed by the // Ethereum protocol implementation. -func (s *LightEthereum) Start(srvr p2p.Server) error { +func (s *LightEthereum) Start(srvr *p2p.Server) error { log.Warn("Light client mode is an experimental feature") s.netRPCService = ethapi.NewPublicNetAPI(srvr, s.networkId) s.protocolManager.Start(srvr) diff --git a/les/handler.go b/les/handler.go index 5af89d4fca..64023af0f5 100644 --- a/les/handler.go +++ b/les/handler.go @@ -61,10 +61,6 @@ const ( disableClientRemovePeer = false ) -type discV5Server interface { - DiscV5() *discv5.Network -} - // errIncompatibleConfig is returned if the requested protocols and configs are // not compatible (low protocol version restrictions and high requirements). var errIncompatibleConfig = errors.New("incompatible configuration") @@ -260,10 +256,10 @@ func (pm *ProtocolManager) removePeer(id string) { } } -func (pm *ProtocolManager) Start(srvr p2p.Server) { +func (pm *ProtocolManager) Start(srvr *p2p.Server) { var topicDisc *discv5.Network - if v, ok := srvr.(discV5Server); ok { - topicDisc = v.DiscV5() + if srvr != nil { + topicDisc = srvr.DiscV5 } lesTopic := discv5.Topic("LES@" + common.Bytes2Hex(pm.blockchain.Genesis().Hash().Bytes()[0:8])) if pm.lightSync { diff --git a/les/server.go b/les/server.go index 1be61ef53a..22fe59b7ac 100644 --- a/les/server.go +++ b/les/server.go @@ -68,7 +68,7 @@ func (s *LesServer) Protocols() []p2p.Protocol { } // Start starts the LES server -func (s *LesServer) Start(srvr p2p.Server) { +func (s *LesServer) Start(srvr *p2p.Server) { s.protocolManager.Start(srvr) } diff --git a/les/serverpool.go b/les/serverpool.go index 14a7354261..64fe991c63 100644 --- a/les/serverpool.go +++ b/les/serverpool.go @@ -97,7 +97,7 @@ const ( type serverPool struct { db ethdb.Database dbKey []byte - server p2p.Server + server *p2p.Server quit chan struct{} wg *sync.WaitGroup connWg sync.WaitGroup @@ -118,7 +118,7 @@ type serverPool struct { } // newServerPool creates a new serverPool instance -func newServerPool(db ethdb.Database, dbPrefix []byte, server p2p.Server, topic discv5.Topic, quit chan struct{}, wg *sync.WaitGroup) *serverPool { +func newServerPool(db ethdb.Database, dbPrefix []byte, server *p2p.Server, topic discv5.Topic, quit chan struct{}, wg *sync.WaitGroup) *serverPool { pool := &serverPool{ db: db, dbKey: append(dbPrefix, []byte(topic)...), @@ -139,11 +139,11 @@ func newServerPool(db ethdb.Database, dbPrefix []byte, server p2p.Server, topic pool.loadNodes() pool.checkDial() - if srv, ok := pool.server.(discV5Server); ok && srv.DiscV5() != nil { + if pool.server.DiscV5 != nil { pool.discSetPeriod = make(chan time.Duration, 1) pool.discNodes = make(chan *discv5.Node, 100) pool.discLookups = make(chan bool, 100) - go srv.DiscV5().SearchTopic(topic, pool.discSetPeriod, pool.discNodes, pool.discLookups) + go pool.server.DiscV5.SearchTopic(topic, pool.discSetPeriod, pool.discNodes, pool.discLookups) } go pool.eventLoop() diff --git a/node/api.go b/node/api.go index ba8cc1b8b7..1b2ef2ed90 100644 --- a/node/api.go +++ b/node/api.go @@ -375,7 +375,7 @@ func NewPublicWeb3API(stack *Node) *PublicWeb3API { // ClientVersion returns the node name func (s *PublicWeb3API) ClientVersion() string { - return s.stack.serverConfig.Name + return s.stack.Server().Name } // Sha3 applies the ethereum sha3 implementation on the input. diff --git a/node/node.go b/node/node.go index 7b4988a9cf..a89110599f 100644 --- a/node/node.go +++ b/node/node.go @@ -56,7 +56,7 @@ type Node struct { instanceDirLock storage.Storage // prevents concurrent use of instance directory serverConfig p2p.Config - server p2p.Server // Currently running P2P networking layer + server *p2p.Server // Currently running P2P networking layer serviceFuncs []ServiceConstructor // Service constructors (in dependency order) services map[reflect.Type]Service // Currently running services @@ -165,6 +165,8 @@ func (n *Node) Start() error { if n.serverConfig.NodeDatabase == "" { n.serverConfig.NodeDatabase = n.config.NodeDB() } + running := &p2p.Server{Config: n.serverConfig} + log.Info("Starting peer-to-peer node", "instance", n.serverConfig.Name) // Otherwise copy and specialize the P2P configuration services := make(map[reflect.Type]Service) @@ -192,10 +194,8 @@ func (n *Node) Start() error { } // Gather the protocols and start the freshly assembled P2P server for _, service := range services { - n.serverConfig.Protocols = append(n.serverConfig.Protocols, service.Protocols()...) + running.Protocols = append(running.Protocols, service.Protocols()...) } - running := p2p.NewServer(n.serverConfig) - log.Info("Starting peer-to-peer node", "instance", n.serverConfig.Name) if err := running.Start(); err != nil { if errno, ok := err.(syscall.Errno); ok && datadirInUseErrnos[uint(errno)] { return ErrDatadirUsed @@ -582,7 +582,7 @@ func (n *Node) RPCHandler() (*rpc.Server, error) { // Server retrieves the currently running P2P network layer. This method is meant // only to inspect fields of the currently running server, life cycle management // should be left to this Node entity. -func (n *Node) Server() p2p.Server { +func (n *Node) Server() *p2p.Server { n.lock.RLock() defer n.lock.RUnlock() diff --git a/node/node_example_test.go b/node/node_example_test.go index ddf76faea7..ee06f4065c 100644 --- a/node/node_example_test.go +++ b/node/node_example_test.go @@ -37,7 +37,7 @@ type SampleService struct{} func (s *SampleService) Protocols() []p2p.Protocol { return nil } func (s *SampleService) APIs() []rpc.API { return nil } -func (s *SampleService) Start(p2p.Server) error { fmt.Println("Service starting..."); return nil } +func (s *SampleService) Start(*p2p.Server) error { fmt.Println("Service starting..."); return nil } func (s *SampleService) Stop() error { fmt.Println("Service stopping..."); return nil } func ExampleService() { diff --git a/node/node_test.go b/node/node_test.go index a0f13ca570..2880efa619 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -154,7 +154,7 @@ func TestServiceLifeCycle(t *testing.T) { id := id // Closure for the constructor constructor := func(*ServiceContext) (Service, error) { return &InstrumentedService{ - startHook: func(p2p.Server) { started[id] = true }, + startHook: func(*p2p.Server) { started[id] = true }, stopHook: func() { stopped[id] = true }, }, nil } @@ -200,7 +200,7 @@ func TestServiceRestarts(t *testing.T) { running = false return &InstrumentedService{ - startHook: func(p2p.Server) { + startHook: func(*p2p.Server) { if running { panic("already running") } @@ -250,7 +250,7 @@ func TestServiceConstructionAbortion(t *testing.T) { id := id // Closure for the constructor constructor := func(*ServiceContext) (Service, error) { return &InstrumentedService{ - startHook: func(p2p.Server) { started[id] = true }, + startHook: func(*p2p.Server) { started[id] = true }, }, nil } if err := stack.Register(maker(constructor)); err != nil { @@ -299,7 +299,7 @@ func TestServiceStartupAbortion(t *testing.T) { id := id // Closure for the constructor constructor := func(*ServiceContext) (Service, error) { return &InstrumentedService{ - startHook: func(p2p.Server) { started[id] = true }, + startHook: func(*p2p.Server) { started[id] = true }, stopHook: func() { stopped[id] = true }, }, nil } @@ -352,7 +352,7 @@ func TestServiceTerminationGuarantee(t *testing.T) { id := id // Closure for the constructor constructor := func(*ServiceContext) (Service, error) { return &InstrumentedService{ - startHook: func(p2p.Server) { started[id] = true }, + startHook: func(*p2p.Server) { started[id] = true }, stopHook: func() { stopped[id] = true }, }, nil } diff --git a/node/service.go b/node/service.go index 05320b75ab..5e1eb0e645 100644 --- a/node/service.go +++ b/node/service.go @@ -86,7 +86,7 @@ type Service interface { // Start is called after all services have been constructed and the networking // layer was also initialized to spawn any goroutines required by the service. - Start(server p2p.Server) error + Start(server *p2p.Server) error // Stop terminates all goroutines belonging to the service, blocking until they // are all terminated. diff --git a/node/utils_test.go b/node/utils_test.go index e9c999b93c..7cdfc2b3aa 100644 --- a/node/utils_test.go +++ b/node/utils_test.go @@ -31,7 +31,7 @@ type NoopService struct{} func (s *NoopService) Protocols() []p2p.Protocol { return nil } func (s *NoopService) APIs() []rpc.API { return nil } -func (s *NoopService) Start(p2p.Server) error { return nil } +func (s *NoopService) Start(*p2p.Server) error { return nil } func (s *NoopService) Stop() error { return nil } func NewNoopService(*ServiceContext) (Service, error) { return new(NoopService), nil } @@ -57,7 +57,7 @@ type InstrumentedService struct { stop error protocolsHook func() - startHook func(p2p.Server) + startHook func(*p2p.Server) stopHook func() } @@ -74,7 +74,7 @@ func (s *InstrumentedService) APIs() []rpc.API { return s.apis } -func (s *InstrumentedService) Start(server p2p.Server) error { +func (s *InstrumentedService) Start(server *p2p.Server) error { if s.startHook != nil { s.startHook(server) } diff --git a/p2p/dial.go b/p2p/dial.go index b832fc233e..9d58caa103 100644 --- a/p2p/dial.go +++ b/p2p/dial.go @@ -47,6 +47,19 @@ const ( maxResolveDelay = time.Hour ) +type NodeDialer interface { + Dial(*discover.Node) (net.Conn, error) +} + +type TCPDialer struct { + *net.Dialer +} + +func (t TCPDialer) Dial(dest *discover.Node) (net.Conn, error) { + addr := &net.TCPAddr{IP: dest.IP, Port: int(dest.TCP)} + return t.Dialer.Dial("tcp", addr.String()) +} + // dialstate schedules dials and discovery lookups. // it get's a chance to compute new tasks on every iteration // of the main loop in server.run. @@ -84,7 +97,7 @@ type pastDial struct { } type task interface { - Do(*server) + Do(*Server) } // A dialTask is generated for each node that is dialed. Its @@ -267,7 +280,7 @@ func (s *dialstate) taskDone(t task, now time.Time) { } } -func (t *dialTask) Do(srv *server) { +func (t *dialTask) Do(srv *Server) { if t.dest.Incomplete() { if !t.resolve(srv) { return @@ -288,7 +301,7 @@ func (t *dialTask) Do(srv *server) { // Resolve operations are throttled with backoff to avoid flooding the // discovery network with useless queries for nodes that don't exist. // The backoff delay resets when the node is found. -func (t *dialTask) resolve(srv *server) bool { +func (t *dialTask) resolve(srv *Server) bool { if srv.ntab == nil { log.Debug("Can't resolve node", "id", t.dest.ID, "err", "discovery is disabled") return false @@ -317,15 +330,14 @@ func (t *dialTask) resolve(srv *server) bool { } // dial performs the actual connection attempt. -func (t *dialTask) dial(srv *server, dest *discover.Node) bool { - addr := &net.TCPAddr{IP: dest.IP, Port: int(dest.TCP)} - fd, err := srv.Dialer.Dial("tcp", addr.String()) +func (t *dialTask) dial(srv *Server, dest *discover.Node) bool { + fd, err := srv.Dialer.Dial(dest) if err != nil { log.Trace("Dial error", "task", t, "err", err) return false } mfd := newMeteredConn(fd, false) - srv.setupConn(mfd, t.flags, dest) + srv.SetupConn(mfd, t.flags, dest) return true } @@ -333,7 +345,7 @@ func (t *dialTask) String() string { return fmt.Sprintf("%v %x %v:%d", t.flags, t.dest.ID[:8], t.dest.IP, t.dest.TCP) } -func (t *discoverTask) Do(srv *server) { +func (t *discoverTask) Do(srv *Server) { // newTasks generates a lookup task whenever dynamic dials are // necessary. Lookups need to take some time, otherwise the // event loop spins too fast. @@ -355,7 +367,7 @@ func (t *discoverTask) String() string { return s } -func (t waitExpireTask) Do(*server) { +func (t waitExpireTask) Do(*Server) { time.Sleep(t.Duration) } func (t waitExpireTask) String() string { diff --git a/p2p/dial_test.go b/p2p/dial_test.go index 55c0154f80..ad18ef9abe 100644 --- a/p2p/dial_test.go +++ b/p2p/dial_test.go @@ -597,8 +597,8 @@ func TestDialResolve(t *testing.T) { } // Now run the task, it should resolve the ID once. - config := Config{Dialer: &net.Dialer{Deadline: time.Now().Add(-5 * time.Minute)}} - srv := &server{ntab: table, Config: config} + config := Config{Dialer: TCPDialer{&net.Dialer{Deadline: time.Now().Add(-5 * time.Minute)}}} + srv := &Server{ntab: table, Config: config} tasks[0].Do(srv) if !reflect.DeepEqual(table.resolveCalls, []discover.NodeID{dest.ID}) { t.Fatalf("wrong resolve calls, got %v", table.resolveCalls) diff --git a/p2p/protocols/protocol.go b/p2p/protocols/protocol.go index 4318341e67..bbe4198455 100644 --- a/p2p/protocols/protocol.go +++ b/p2p/protocols/protocol.go @@ -30,12 +30,13 @@ Standard protocol supports: package protocols import ( + "context" "fmt" "reflect" + "sync" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/discover" ) // error codes used by this protocol scheme @@ -45,23 +46,21 @@ const ( ErrWrite ErrInvalidMsgCode ErrInvalidMsgType - ErrLocalHandshake - ErrRemoteHandshake + ErrHandshake ErrNoHandler ErrHandler ) // error description strings associated with the codes var errorToString = map[int]string{ - ErrMsgTooLong: "Message too long", - ErrDecode: "Invalid message (RLP error)", - ErrWrite: "Error sending message", - ErrInvalidMsgCode: "Invalid message code", - ErrInvalidMsgType: "Invalid message type", - ErrLocalHandshake: "Local handshake error", - ErrRemoteHandshake: "Remote handshake error", - ErrNoHandler: "No handler registered error", - ErrHandler: "Message handler error", + ErrMsgTooLong: "Message too long", + ErrDecode: "Invalid message (RLP error)", + ErrWrite: "Error sending message", + ErrInvalidMsgCode: "Invalid message code", + ErrInvalidMsgType: "Invalid message type", + ErrHandshake: "Handshake error", + ErrNoHandler: "No handler registered error", + ErrHandler: "Message handler error", } /* @@ -110,93 +109,74 @@ func errorf(code int, format string, params ...interface{}) *Error { return self } -// implements the code table spec -// listing the message codes and types etc -// and further metadata about the protocol -type CodeMap struct { - Name string // name of the protocol - Version uint // version - MaxMsgSize int // max length of message payload size - codes []reflect.Type // index of codes to msg types - to create zero values - messages map[reflect.Type]uint64 // index of types to codes, for sending by type +// Spec is a protocol specification including its name and version as well as +// the types of messages which are exchanged +type Spec struct { + // Name is the name of the protocol, often a three-letter word + Name string + + // Version is the version number of the protocol + Version uint + + // MaxMsgSize is the maximum accepted length of the message payload + MaxMsgSize uint32 + + // Messages is a list of message types which this protocol uses, with + // each message type being sent with its array index as the code (so + // [&foo{}, &bar{}, &baz{}] would send foo, bar and baz with codes + // 0, 1 and 2 respectively) + Messages []interface{} + + initOnce sync.Once + codes map[reflect.Type]uint64 + types map[uint64]reflect.Type } -func (self *CodeMap) GetInterface(code uint64) (interface{}, bool) { - if int(code) > len(self.codes)-1 { +func (s *Spec) init() { + s.initOnce.Do(func() { + s.codes = make(map[reflect.Type]uint64, len(s.Messages)) + s.types = make(map[uint64]reflect.Type, len(s.Messages)) + for i, msg := range s.Messages { + code := uint64(i) + typ := reflect.TypeOf(msg) + if typ.Kind() == reflect.Ptr { + typ = typ.Elem() + } + s.codes[typ] = code + s.types[code] = typ + } + }) +} + +func (s *Spec) Length() uint64 { + return uint64(len(s.Messages)) +} + +func (s *Spec) GetCode(msg interface{}) (uint64, bool) { + s.init() + typ := reflect.TypeOf(msg) + if typ.Kind() == reflect.Ptr { + typ = typ.Elem() + } + code, ok := s.codes[typ] + return code, ok +} + +func (s *Spec) NewMsg(code uint64) (interface{}, bool) { + s.init() + typ, ok := s.types[code] + if !ok { return nil, false } - typ := self.codes[code] - val := reflect.New(typ) - return val.Interface(), true -} - -func (self *CodeMap) GetCode(msg interface{}) (uint64, bool) { - code, found := self.messages[reflect.TypeOf(msg)] - return code, found -} - -func NewCodeMap(name string, version uint, maxMsgSize int, msgs ...interface{}) *CodeMap { - self := &CodeMap{ - Name: name, - Version: version, - MaxMsgSize: maxMsgSize, - messages: make(map[reflect.Type]uint64), - } - self.Register(msgs...) - return self -} - -func (self *CodeMap) Length() uint64 { - return uint64(len(self.codes)) -} - -func (self *CodeMap) Register(msgs ...interface{}) { - code := uint64(len(self.codes)) - for _, msg := range msgs { - typ := reflect.TypeOf(msg) - _, found := self.messages[typ] - if found { - // ignore duplicates - continue - } - // next code assigned to message type typ - self.messages[typ] = code - self.codes = append(self.codes, typ) - code++ - } -} - -func NewProtocol(protocolname string, protocolversion uint, run func(*Peer) error, ct *CodeMap, peerInfo func(id discover.NodeID) interface{}, nodeInfo func() interface{}) *p2p.Protocol { - - // PeerInfo is an optional helper method to retrieve protocol specific metadata - // about a certain peer in the network. If an info retrieval function is set, - // but returns nil, it is assumed that the protocol handshake is still running. - r := func(p *p2p.Peer, rw p2p.MsgReadWriter) error { - return run(NewPeer(p, ct, rw)) - - } - - return &p2p.Protocol{ - Name: protocolname, - Version: protocolversion, - Length: ct.Length(), - Run: r, - PeerInfo: peerInfo, - NodeInfo: nodeInfo, - } -} - -type Disconnect struct { - err error + return reflect.New(typ).Interface(), true } // A Peer represents a remote peer or protocol instance that is running on a peer connection with // a remote peer type Peer struct { - ct *CodeMap // CodeMap for the protocol - *p2p.Peer // the p2p.Peer object representing the remote - rw p2p.MsgReadWriter // p2p.MsgReadWriter to send messages to and read messages from - handlers map[reflect.Type][]func(interface{}) error // message type -> message handler callback(s) map + *p2p.Peer // the p2p.Peer object representing the remote + rw p2p.MsgReadWriter // p2p.MsgReadWriter to send messages to and read messages from + spec *Spec Errc chan error wErrc chan error // write error channel } @@ -205,55 +185,28 @@ type Peer struct { // this constructor is called by the p2p.Protocol#Run function // the first two arguments are comming the arguments passed to p2p.Protocol.Run function // the third argument is the CodeMap describing the protocol messages and options -func NewPeer(p *p2p.Peer, ct *CodeMap, rw p2p.MsgReadWriter) *Peer { +func NewPeer(p *p2p.Peer, rw p2p.MsgReadWriter, spec *Spec) *Peer { return &Peer{ - ct: ct, - Peer: p, - rw: rw, - Errc: make(chan error), - wErrc: make(chan error), - handlers: make(map[reflect.Type][]func(interface{}) error), + Peer: p, + rw: rw, + spec: spec, + Errc: make(chan error), + wErrc: make(chan error), } } -// Register is called on the peer typically within the constructor of service instances running on peer connections -// These constructors are called by the p2p.Protocol#Run function -// It ties handler callbackss for specific message types -// A message type can have several handlers registered by the same or different protocol services -// Register is meant to be called once, deregistering is not currently supported therefore -// handlers are assumed to be static across handshake renegotiations -// i.e., a service instance either handles a message or not (irrespective of the handshake) -// it panics if the message type is not defined in the CodeMap -func (self *Peer) Register(msg interface{}, handler func(interface{}) error) uint64 { - typ := reflect.TypeOf(msg) - code, found := self.ct.messages[typ] - if !found { - panic(fmt.Sprintf("message type '%v' unknown ", typ)) - } - log.Trace(fmt.Sprintf("register handle for %v", typ)) - self.handlers[typ] = append(self.handlers[typ], handler) - return code -} - // Run starts the forever loop that handles incoming messages // called within the p2p.Protocol#Run function -func (self *Peer) Run() error { +func (self *Peer) Run(handler func(msg interface{}) error) error { go func() { for { - _, err := self.handleIncoming() - if err != nil { + if err := self.handleIncoming(handler); err != nil { self.Errc <- err return } } }() - err := <-self.Errc - d := &Disconnect{err} - for _, f := range self.handlers[reflect.TypeOf(d)] { - log.Trace(fmt.Sprintf("disconnect hook for %v", d)) - f(err) - } - return err + return <-self.Errc } // Drop disconnects a peer. @@ -271,20 +224,12 @@ func (self *Peer) Drop(err error) { // this low level call will be wrapped by libraries providing routed or broadcast sends // but often just used to forward and push messages to directly connected peers func (self *Peer) Send(msg interface{}) error { - code, found := self.ct.GetCode(msg) + code, found := self.spec.GetCode(msg) if !found { return errorf(ErrInvalidMsgType, "%v", code) } log.Trace(fmt.Sprintf("=> msg #%d TO %v : %v", code, self.ID(), msg)) - return p2p.Send(self.rw, uint64(code), msg) -} - -func (self *Peer) DisconnectHook(f func(error)) { - typ := reflect.TypeOf(&Disconnect{}) - self.handlers[typ] = append(self.handlers[typ], func(e interface{}) error { - f(e.(error)) - return nil - }) + return p2p.Send(self.rw, code, msg) } // handleIncoming(code) @@ -292,79 +237,72 @@ func (self *Peer) DisconnectHook(f func(error)) { // if this returns an error the loop returns and the peer is disconnected with the error // checks message size, out-of-range message codes, handles decoding with reflection, // call handlers as callback onside -func (self *Peer) handleIncoming() (interface{}, error) { +func (self *Peer) handleIncoming(handle func(msg interface{}) error) error { msg, err := self.rw.ReadMsg() if err != nil { - return nil, err + return err } log.Trace(fmt.Sprintf("<= %v", msg)) // make sure that the payload has been fully consumed defer msg.Discard() - if msg.Size > uint32(self.ct.MaxMsgSize) { - return nil, errorf(ErrMsgTooLong, "%v > %v", msg.Size, self.ct.MaxMsgSize) + if msg.Size > self.spec.MaxMsgSize { + return errorf(ErrMsgTooLong, "%v > %v", msg.Size, self.spec.MaxMsgSize) } - // check if the message code is correct - maxMsgCode := uint(len(self.ct.messages)) - if msg.Code >= uint64(maxMsgCode) { - return nil, errorf(ErrInvalidMsgCode, "%v (>=%v)", msg.Code, maxMsgCode) + val, ok := self.spec.NewMsg(msg.Code) + if !ok { + return errorf(ErrInvalidMsgCode, "%v", msg.Code) } - - // it is safe to be unsafe here - typ := self.ct.codes[msg.Code] - val := reflect.New(typ) - req := val.Elem() - req.Set(reflect.Zero(typ)) - if err := msg.Decode(val.Interface()); err != nil { - return nil, errorf(ErrDecode, "<= %v: %v", msg, err) + if err := msg.Decode(val); err != nil { + return errorf(ErrDecode, "<= %v: %v", msg, err) } - log.Trace(fmt.Sprintf("<= %v FROM %v %v %v", msg, self.ID(), req, typ)) + log.Trace(fmt.Sprintf("<= %v FROM %v %T %v", msg, self.ID(), val, val)) // call the registered handler callbacks // a registered callback take the decoded message as argument as an interface // which the handler is supposed to cast to the appropriate type // it is entirely safe not to check the cast in the handler since the handler is // chosen based on the proper type in the first place - handlers := self.handlers[typ] - if len(handlers) == 0 { - log.Trace(fmt.Sprintf("no handler (msg code %v)", msg.Code)) - // return nil, errorf(ErrNoHandler, "(msg code %v)", msg.Code) - } else { - for i, f := range handlers { - log.Trace(fmt.Sprintf("handler %v for %v", i, typ)) - err = f(req.Interface()) - if err != nil { - return nil, errorf(ErrHandler, "(msg code %v): %v", msg.Code, err) - } - } + if err := handle(val); err != nil { + return errorf(ErrHandler, "(msg code %v): %v", msg.Code, err) } - return req.Interface(), nil + return nil } // Handshake initiates a handshake on the peer connection // * the argument is the local handshake to be sent to the remote peer // * expects a remote handshake back of the same type // returns the remote hs and an error -func (self *Peer) Handshake(hs interface{}) (interface{}, error) { - typ := reflect.TypeOf(hs) - _, found := self.ct.messages[typ] - if !found { - return nil, errorf(ErrLocalHandshake, "unknown handshake message type: %v", typ) +func (self *Peer) Handshake(ctx context.Context, hs interface{}) (interface{}, error) { + if _, ok := self.spec.GetCode(hs); !ok { + return nil, errorf(ErrHandshake, "unknown handshake message type: %T", hs) } - errc := make(chan error) + errc := make(chan error, 2) go func() { - err := self.Send(hs) - if err != nil { - err = errorf(ErrLocalHandshake, "cannot send: %v", err) + if err := self.Send(hs); err != nil { + errc <- errorf(ErrHandshake, "cannot send: %v", err) } - errc <- err }() - // receiving and validating remote handshake, expect code - rhs, err := self.handleIncoming() - if err != nil { - return nil, errorf(ErrRemoteHandshake, "'%v': %v", self.ct.Name, err) + hsc := make(chan interface{}) + go func() { + var rhs interface{} + err := self.handleIncoming(func(msg interface{}) error { + rhs = msg + return nil + }) + if err != nil { + errc <- err + return + } + hsc <- rhs + }() + select { + case rhs := <-hsc: + return rhs, nil + case <-ctx.Done(): + return nil, ctx.Err() + case err := <-errc: + return nil, err } - err = <-errc - return rhs, err } diff --git a/p2p/protocols/protocol_test.go b/p2p/protocols/protocol_test.go index 3ca2fcb0d9..e4f7a145a1 100644 --- a/p2p/protocols/protocol_test.go +++ b/p2p/protocols/protocol_test.go @@ -1,6 +1,8 @@ package protocols import ( + "context" + "errors" "fmt" "os" "testing" @@ -13,7 +15,7 @@ import ( ) func init() { - log.Root().SetHandler(log.LvlFilterHandler(log.LvlError, log.StreamHandler(os.Stderr, log.TerminalFormat(false)))) + log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(false)))) } // handshake message type @@ -55,28 +57,26 @@ const networkId = "420" // newProtocol sets up a protocol // the run function here demonstrates a typical protocol using peerPool, handshake // and messages registered to handlers -func newProtocol(pp *p2ptest.TestPeerPool) adapters.RunProtocol { - ct := NewCodeMap("test", 42, 1024, &protoHandshake{}, &hs0{}, &kill{}, &drop{}) +func newProtocol(pp *p2ptest.TestPeerPool) func(*p2p.Peer, p2p.MsgReadWriter) error { + spec := &Spec{ + Name: "test", + Version: 42, + MaxMsgSize: 10 * 1024, + Messages: []interface{}{ + protoHandshake{}, + hs0{}, + kill{}, + drop{}, + }, + } return func(p *p2p.Peer, rw p2p.MsgReadWriter) error { - peer := NewPeer(p, ct, rw) - - // demonstrates use of peerPool, killing another peer connection as a response to a message - peer.Register(&kill{}, func(msg interface{}) error { - id := msg.(*kill).C - pp.Get(id).Drop(fmt.Errorf("killed")) - log.Trace(fmt.Sprintf("id %v killed", id)) - return nil - }) - - // for testing we can trigger self induced disconnect upon receiving drop message - peer.Register(&drop{}, func(msg interface{}) error { - log.Trace("dropped") - return fmt.Errorf("dropped") - }) + peer := NewPeer(p, rw, spec) // initiate one-off protohandshake and check validity - phs := &protoHandshake{ct.Version, networkId} - hs, err := peer.Handshake(phs) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + phs := &protoHandshake{42, networkId} + hs, err := peer.Handshake(ctx, phs) if err != nil { return err } @@ -88,7 +88,7 @@ func newProtocol(pp *p2ptest.TestPeerPool) adapters.RunProtocol { lhs := &hs0{42} // module handshake demonstrating a simple repeatable exchange of same-type message - hs, err = peer.Handshake(lhs) + hs, err = peer.Handshake(ctx, lhs) if err != nil { return err } @@ -97,19 +97,40 @@ func newProtocol(pp *p2ptest.TestPeerPool) adapters.RunProtocol { return fmt.Errorf("handshake mismatch remote %v > local %v", rmhs.C, lhs.C) } - peer.Register(lhs, func(msg interface{}) error { - rhs := msg.(*hs0) - if rhs.C > lhs.C { - return fmt.Errorf("handshake mismatch remote %v > local %v", rhs.C, lhs.C) + handle := func(msg interface{}) error { + switch msg := msg.(type) { + + case *protoHandshake: + return errors.New("duplicate handshake") + + case *hs0: + rhs := msg + if rhs.C > lhs.C { + return fmt.Errorf("handshake mismatch remote %v > local %v", rhs.C, lhs.C) + } + lhs.C += rhs.C + return peer.Send(lhs) + + case *kill: + // demonstrates use of peerPool, killing another peer connection as a response to a message + id := msg.C + pp.Get(id).Drop(errors.New("killed")) + log.Trace(fmt.Sprintf("id %v killed", id)) + return nil + + case *drop: + // for testing we can trigger self induced disconnect upon receiving drop message + return errors.New("dropped") + + default: + return fmt.Errorf("unknown message type: %T", msg) } - lhs.C += rhs.C - return peer.Send(lhs) - }) + } log.Trace(fmt.Sprintf("adding peer %v", peer)) pp.Add(peer) defer pp.Remove(peer) - err = peer.Run() + err = peer.Run(handle) log.Trace(fmt.Sprintf("peer %v protocol quitting: %v", peer, err)) return err diff --git a/p2p/server.go b/p2p/server.go index c641e3a628..eb7c70e593 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -131,7 +131,7 @@ type Config struct { // If Dialer is set to a non-nil value, the given Dialer // is used to dial outbound peer connections. - Dialer *net.Dialer `toml:"-"` + Dialer NodeDialer `toml:"-"` // If NoDial is true, the server will not dial any peers. NoDial bool `toml:",omitempty"` @@ -141,23 +141,8 @@ type Config struct { EnableMsgEvents bool } -type Server interface { - Start() error - Stop() error - AddPeer(node *discover.Node) - RemovePeer(node *discover.Node) - SubscribeEvents(ch chan *PeerEvent) event.Subscription - PeerCount() int - NodeInfo() *NodeInfo - PeersInfo() []*PeerInfo -} - -func NewServer(conf Config) Server { - return &server{Config: conf} -} - // Server manages all peer connections. -type server struct { +type Server struct { // Config fields may not be modified while the server is running. Config @@ -266,7 +251,7 @@ func (c *conn) is(f connFlag) bool { } // Peers returns all connected peers. -func (srv *server) Peers() []*Peer { +func (srv *Server) Peers() []*Peer { var ps []*Peer select { // Note: We'd love to put this function into a variable but @@ -284,7 +269,7 @@ func (srv *server) Peers() []*Peer { } // PeerCount returns the number of connected peers. -func (srv *server) PeerCount() int { +func (srv *Server) PeerCount() int { var count int select { case srv.peerOp <- func(ps map[discover.NodeID]*Peer) { count = len(ps) }: @@ -297,7 +282,7 @@ func (srv *server) PeerCount() int { // AddPeer connects to the given node and maintains the connection until the // server is shut down. If the connection fails for any reason, the server will // attempt to reconnect the peer. -func (srv *server) AddPeer(node *discover.Node) { +func (srv *Server) AddPeer(node *discover.Node) { select { case srv.addstatic <- node: case <-srv.quit: @@ -305,7 +290,7 @@ func (srv *server) AddPeer(node *discover.Node) { } // RemovePeer disconnects from the given node -func (srv *server) RemovePeer(node *discover.Node) { +func (srv *Server) RemovePeer(node *discover.Node) { select { case srv.removestatic <- node: case <-srv.quit: @@ -313,12 +298,12 @@ func (srv *server) RemovePeer(node *discover.Node) { } // SubscribePeers subscribes the given channel to peer events -func (srv *server) SubscribeEvents(ch chan *PeerEvent) event.Subscription { +func (srv *Server) SubscribeEvents(ch chan *PeerEvent) event.Subscription { return srv.peerFeed.Subscribe(ch) } // Self returns the local node's endpoint information. -func (srv *server) Self() *discover.Node { +func (srv *Server) Self() *discover.Node { srv.lock.Lock() defer srv.lock.Unlock() @@ -328,7 +313,7 @@ func (srv *server) Self() *discover.Node { return srv.makeSelf(srv.listener, srv.ntab) } -func (srv *server) makeSelf(listener net.Listener, ntab discoverTable) *discover.Node { +func (srv *Server) makeSelf(listener net.Listener, ntab discoverTable) *discover.Node { // If the server's not running, return an empty node. // If the node is running but discovery is off, manually assemble the node infos. if ntab == nil { @@ -350,7 +335,7 @@ func (srv *server) makeSelf(listener net.Listener, ntab discoverTable) *discover // Stop terminates the server and all active peer connections. // It blocks until all active connections have been closed. -func (srv *server) Stop() error { +func (srv *Server) Stop() error { srv.lock.Lock() defer srv.lock.Unlock() if !srv.running { @@ -368,7 +353,7 @@ func (srv *server) Stop() error { // Start starts running the server. // Servers can not be re-used after stopping. -func (srv *server) Start() (err error) { +func (srv *Server) Start() (err error) { srv.lock.Lock() defer srv.lock.Unlock() if srv.running { @@ -385,7 +370,7 @@ func (srv *server) Start() (err error) { srv.newTransport = newRLPX } if srv.Dialer == nil { - srv.Dialer = &net.Dialer{Timeout: defaultDialTimeout} + srv.Dialer = TCPDialer{&net.Dialer{Timeout: defaultDialTimeout}} } srv.quit = make(chan struct{}) srv.addpeer = make(chan *conn) @@ -446,7 +431,7 @@ func (srv *server) Start() (err error) { return nil } -func (srv *server) startListening() error { +func (srv *Server) startListening() error { // Launch the TCP listener. listener, err := net.Listen("tcp", srv.ListenAddr) if err != nil { @@ -475,7 +460,7 @@ type dialer interface { removeStatic(*discover.Node) } -func (srv *server) run(dialstate dialer) { +func (srv *Server) run(dialstate dialer) { defer srv.loopWG.Done() var ( peers = make(map[discover.NodeID]*Peer) @@ -616,7 +601,7 @@ running: } } -func (srv *server) protoHandshakeChecks(peers map[discover.NodeID]*Peer, c *conn) error { +func (srv *Server) protoHandshakeChecks(peers map[discover.NodeID]*Peer, c *conn) error { // Drop connections with no matching protocols. if len(srv.Protocols) > 0 && countMatchingProtocols(srv.Protocols, c.caps) == 0 { return DiscUselessPeer @@ -626,7 +611,7 @@ func (srv *server) protoHandshakeChecks(peers map[discover.NodeID]*Peer, c *conn return srv.encHandshakeChecks(peers, c) } -func (srv *server) encHandshakeChecks(peers map[discover.NodeID]*Peer, c *conn) error { +func (srv *Server) encHandshakeChecks(peers map[discover.NodeID]*Peer, c *conn) error { switch { case !c.is(trustedConn|staticDialedConn) && len(peers) >= srv.MaxPeers: return DiscTooManyPeers @@ -645,7 +630,7 @@ type tempError interface { // listenLoop runs in its own goroutine and accepts // inbound connections. -func (srv *server) listenLoop() { +func (srv *Server) listenLoop() { defer srv.loopWG.Done() log.Info("RLPx listener up", "self", srv.makeSelf(srv.listener, srv.ntab)) @@ -697,7 +682,7 @@ func (srv *server) listenLoop() { // Spawn the handler. It will give the slot back when the connection // has been established. go func() { - srv.setupConn(fd, inboundConn, nil) + srv.SetupConn(fd, inboundConn, nil) slots <- struct{}{} }() } @@ -706,7 +691,7 @@ func (srv *server) listenLoop() { // setupConn runs the handshakes and attempts to add the connection // as a peer. It returns when the connection has been added as a peer // or the handshakes have failed. -func (srv *server) setupConn(fd net.Conn, flags connFlag, dialDest *discover.Node) { +func (srv *Server) SetupConn(fd net.Conn, flags connFlag, dialDest *discover.Node) { // Prevent leftover pending conns from entering the handshake. srv.lock.Lock() running := srv.running @@ -766,7 +751,7 @@ func truncateName(s string) string { // checkpoint sends the conn to run, which performs the // post-handshake checks for the stage (posthandshake, addpeer). -func (srv *server) checkpoint(c *conn, stage chan<- *conn) error { +func (srv *Server) checkpoint(c *conn, stage chan<- *conn) error { select { case stage <- c: case <-srv.quit: @@ -783,7 +768,7 @@ func (srv *server) checkpoint(c *conn, stage chan<- *conn) error { // runPeer runs in its own goroutine for each peer. // it waits until the Peer logic returns and removes // the peer. -func (srv *server) runPeer(p *Peer) { +func (srv *Server) runPeer(p *Peer) { if srv.newPeerHook != nil { srv.newPeerHook(p) } @@ -824,7 +809,7 @@ type NodeInfo struct { } // NodeInfo gathers and returns a collection of metadata known about the host. -func (srv *server) NodeInfo() *NodeInfo { +func (srv *Server) NodeInfo() *NodeInfo { node := srv.Self() // Gather and assemble the generic node infos @@ -853,7 +838,7 @@ func (srv *server) NodeInfo() *NodeInfo { } // PeersInfo returns an array of metadata objects describing connected peers. -func (srv *server) PeersInfo() []*PeerInfo { +func (srv *Server) PeersInfo() []*PeerInfo { // Gather all the generic and sub-protocol specific infos infos := make([]*PeerInfo, 0, srv.PeerCount()) for _, peer := range srv.Peers() { @@ -872,6 +857,6 @@ func (srv *server) PeersInfo() []*PeerInfo { return infos } -func (srv *server) DiscV5() *discv5.Network { +func (srv *Server) DiscV5() *discv5.Network { return srv.discV5 } diff --git a/p2p/server_test.go b/p2p/server_test.go index ca8f1873ee..11dd83e5d6 100644 --- a/p2p/server_test.go +++ b/p2p/server_test.go @@ -72,7 +72,7 @@ func startTestServer(t *testing.T, id discover.NodeID, pf func(*Peer)) *Server { ListenAddr: "127.0.0.1:0", PrivateKey: newkey(), } - server := &server{ + server := &Server{ Config: config, newPeerHook: pf, newTransport: func(fd net.Conn) transport { return newTestTransport(id, fd) }, @@ -201,7 +201,7 @@ func TestServerTaskScheduling(t *testing.T) { // The Server in this test isn't actually running // because we're only interested in what run does. - srv := &server{ + srv := &Server{ Config: Config{MaxPeers: 10}, quit: make(chan struct{}), ntab: fakeTable{}, @@ -246,7 +246,7 @@ func TestServerManyTasks(t *testing.T) { } var ( - srv = &server{quit: make(chan struct{}), ntab: fakeTable{}, running: true} + srv = &Server{quit: make(chan struct{}), ntab: fakeTable{}, running: true} done = make(chan *testTask) start, end = 0, 0 ) @@ -317,7 +317,7 @@ func (t *testTask) Do(srv *Server) { // at capacity. Trusted connections should still be accepted. func TestServerAtCap(t *testing.T) { trustedID := randomID() - srv := &server{ + srv := &Server{ Config: Config{ PrivateKey: newkey(), MaxPeers: 10, @@ -420,7 +420,7 @@ func TestServerSetupConn(t *testing.T) { } for i, test := range tests { - srv := &server{ + srv := &Server{ Config: Config{ PrivateKey: srvkey, MaxPeers: 10, @@ -435,7 +435,7 @@ func TestServerSetupConn(t *testing.T) { } } p1, _ := net.Pipe() - srv.setupConn(p1, test.flags, test.dialDest) + srv.SetupConn(p1, test.flags, test.dialDest) if !reflect.DeepEqual(test.tt.closeErr, test.wantCloseErr) { t.Errorf("test %d: close error mismatch: got %q, want %q", i, test.tt.closeErr, test.wantCloseErr) } diff --git a/p2p/simulations/adapters/docker.go b/p2p/simulations/adapters/docker.go index a9460df0df..f90c367bdb 100644 --- a/p2p/simulations/adapters/docker.go +++ b/p2p/simulations/adapters/docker.go @@ -9,6 +9,7 @@ import ( "os/exec" "path/filepath" "runtime" + "strings" "github.com/docker/docker/pkg/reexec" "github.com/ethereum/go-ethereum/node" @@ -81,12 +82,16 @@ func (n *DockerNode) dockerCommand() *exec.Cmd { return exec.Command( "sh", "-c", fmt.Sprintf( - `exec docker run --interactive --env _P2P_NODE_CONFIG="${_P2P_NODE_CONFIG}" %s p2p-node %s %s`, - dockerImage, n.Config.Node.Service, n.ID.String(), + `exec docker run --interactive --env _P2P_NODE_CONFIG="${_P2P_NODE_CONFIG}" --env _P2P_NODE_KEY="${_P2P_NODE_KEY}" %s p2p-node %s %s`, + dockerImage, strings.Join(n.Services, " "), n.ID.String(), ), ) } +func (n *DockerNode) GetService(name string) node.Service { + return nil +} + // dockerImage is the name of the docker image const dockerImage = "p2p-node" diff --git a/p2p/simulations/adapters/exec.go b/p2p/simulations/adapters/exec.go index 706cdcfd9f..a535e80698 100644 --- a/p2p/simulations/adapters/exec.go +++ b/p2p/simulations/adapters/exec.go @@ -63,7 +63,7 @@ func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) { Node: config, } conf.Stack.DataDir = filepath.Join(dir, "data") - conf.Stack.P2P.EnableMsgEvents = true + conf.Stack.P2P.EnableMsgEvents = false conf.Stack.P2P.NoDiscovery = true conf.Stack.P2P.NAT = nil @@ -88,11 +88,12 @@ func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) { // (so for example we can run the node in a remote Docker container and // still communicate with it). type ExecNode struct { - ID *NodeId - Dir string - Config *execNodeConfig - Cmd *exec.Cmd - Info *p2p.NodeInfo + ID *NodeId + Dir string + Config *execNodeConfig + Cmd *exec.Cmd + Info *p2p.NodeInfo + Services []string client *rpc.Client rpcMux *rpcMux @@ -164,13 +165,17 @@ func (n *ExecNode) Start(snapshot []byte) (err error) { return nil } +func (n *ExecNode) GetService(name string) node.Service { + return nil +} + // execCommand returns a command which runs the node locally by exec'ing // the current binary but setting argv[0] to "p2p-node" so that the child // runs execP2PNode func (n *ExecNode) execCommand() *exec.Cmd { return &exec.Cmd{ Path: reexec.Self(), - Args: []string{"p2p-node", n.Config.Node.Service, n.ID.String()}, + Args: []string{"p2p-node", n.Services[0], n.ID.String()}, } } @@ -278,7 +283,7 @@ func execP2PNode() { if !exists { log.Crit(fmt.Sprintf("unknown node service %q", serviceName)) } - service := serviceFunc(id, conf.Snapshot) + services := serviceFunc(id, conf.Snapshot) // use explicit IP address in ListenAddr so that Enode URL is usable if strings.HasPrefix(conf.Stack.P2P.ListenAddr, ":") { @@ -295,7 +300,7 @@ func execP2PNode() { } // start the devp2p stack - stack, err := startP2PNode(&conf.Stack, service) + stack, err := startP2PNode(&conf.Stack, services) if err != nil { log.Crit("error starting p2p node", "err", err) } @@ -321,17 +326,20 @@ func execP2PNode() { stack.Wait() } -func startP2PNode(conf *node.Config, service node.Service) (*node.Node, error) { +func startP2PNode(conf *node.Config, services []node.Service) (*node.Node, error) { stack, err := node.New(conf) if err != nil { return nil, err } - constructor := func(ctx *node.ServiceContext) (node.Service, error) { - return &snapshotService{service}, nil - } - if err := stack.Register(constructor); err != nil { - return nil, err + for _, svc := range services { + constructor := func(ctx *node.ServiceContext) (node.Service, error) { + return &snapshotService{svc}, nil + } + if err := stack.Register(constructor); err != nil { + return nil, err + } } + if err := stack.Start(); err != nil { return nil, err } diff --git a/p2p/simulations/adapters/inproc.go b/p2p/simulations/adapters/inproc.go index 6f27607cf4..37438ce276 100644 --- a/p2p/simulations/adapters/inproc.go +++ b/p2p/simulations/adapters/inproc.go @@ -17,10 +17,11 @@ package adapters import ( - "context" "errors" "fmt" + "math" "net" + "reflect" "sync" "github.com/ethereum/go-ethereum/event" @@ -36,7 +37,6 @@ import ( type SimAdapter struct { mtx sync.RWMutex nodes map[discover.NodeID]*SimNode - services map[string]ServiceFunc } // NewSimAdapter creates a SimAdapter which is capable of running in-memory @@ -45,7 +45,6 @@ type SimAdapter struct { func NewSimAdapter(services map[string]ServiceFunc) *SimAdapter { return &SimAdapter{ nodes: make(map[discover.NodeID]*SimNode), - services: services, } } @@ -56,6 +55,8 @@ func (s *SimAdapter) Name() string { // NewNode returns a new SimNode using the given config func (s *SimAdapter) NewNode(config *NodeConfig) (Node, error) { + var nodeprotos []p2p.Protocol + s.mtx.Lock() defer s.mtx.Unlock() @@ -66,6 +67,7 @@ func (s *SimAdapter) NewNode(config *NodeConfig) (Node, error) { } // check the service is valid and initialize it +/* serviceFunc, exists := s.services[config.Service] if !exists { return nil, fmt.Errorf("unknown node service %q", config.Service) @@ -73,13 +75,60 @@ func (s *SimAdapter) NewNode(config *NodeConfig) (Node, error) { node := &SimNode{ Id: id, + config: config, adapter: s, serviceFunc: serviceFunc, - peers: make(map[discover.NodeID]MsgReadWriteCloser), - dropPeers: make(chan struct{}), +*/ + //serviceFunc, exists := s.services[config.Service] + + //if !exists { + // return nil, fmt.Errorf("unknown node service %q", config.Service) + //} + //service := serviceFunc(id) + + _, err := node.New(&node.Config{ + P2P: p2p.Config{ + PrivateKey: config.PrivateKey, + MaxPeers: math.MaxInt32, + NoDiscovery: true, + Protocols: nodeprotos, + Dialer: s, + EnableMsgEvents: true, + }, + }) + if err != nil { + return nil, err } - s.nodes[id.NodeID] = node - return node, nil + + for _, service := range serviceFuncs[config.Service](id, nil) { + for _, proto := range service.Protocols() { + nodeprotos = append(nodeprotos, proto) + } + } + + simnode := &SimNode{ + Id: id, + serviceFunc: serviceFuncs[config.Service], + adapter: s, + config: config, + running: []node.Service{}, + } + s.nodes[id.NodeID] = simnode + return simnode, nil +} + +func (s *SimAdapter) Dial(dest *discover.Node) (conn net.Conn, err error) { + node, ok := s.GetNode(dest.ID) + if !ok { + return nil, fmt.Errorf("unknown node: %s", dest.ID) + } + srv := node.Server() + if srv == nil { + return nil, fmt.Errorf("node not running: %s", dest.ID) + } + pipe1, pipe2 := net.Pipe() + go srv.SetupConn(pipe1, 0, nil) + return pipe2, nil } // GetNode returns the node with the given ID if it exists @@ -90,14 +139,6 @@ func (s *SimAdapter) GetNode(id discover.NodeID) (*SimNode, bool) { return node, ok } -// MsgReadWriteCloser wraps a MsgReadWriter with the addition of a Close method -// so we can simulate the closing of a p2p connection (which usually happens by -/// closing the underlying TCP connection) -type MsgReadWriteCloser interface { - p2p.MsgReadWriter - Close() error -} - // SimNode is an in-memory node which connects to other SimNodes using an // in-memory p2p.MsgReadWriter pipe, running an underlying service protocol // directly over that pipe. @@ -107,17 +148,13 @@ type MsgReadWriteCloser interface { type SimNode struct { lock sync.RWMutex Id *NodeId + config *NodeConfig adapter *SimAdapter - running node.Service - serviceFunc ServiceFunc - peers map[discover.NodeID]MsgReadWriteCloser - peerFeed event.Feed + serviceFunc ServiceFunc + node *node.Node client *rpc.Client rpcMux *rpcMux - - // dropPeers is used to force peer disconnects when - // the node is stopped - dropPeers chan struct{} + running []node.Service } // Addr returns the node's discovery address @@ -127,7 +164,7 @@ func (self *SimNode) Addr() []byte { // Node returns a discover.Node representing the SimNode func (self *SimNode) Node() *discover.Node { - return discover.NewNode(self.Id.NodeID, nil, 0, 0) + return discover.NewNode(self.Id.NodeID, net.IP{127, 0, 0, 1}, 30303, 30303) } // Client returns an rpc.Client which can be used to communicate with the @@ -154,82 +191,68 @@ func (self *SimNode) ServeRPC(conn net.Conn) error { return nil } -// Start initializes the service, starts the RPC handler and then starts -// the service -func (self *SimNode) Start(snapshot []byte) error { - service := self.serviceFunc(self.Id, snapshot) +// Snapshot creates a snapshot of the service state by calling the +// simulation_snapshot RPC method +func (self *SimNode) Snapshot() ([]byte, error) { + self.lock.Lock() + defer self.lock.Unlock() + if self.client == nil { + return nil, errors.New("RPC not started") + } + var snapshot []byte + return snapshot, self.client.Call(&snapshot, "simulation_snapshot") +} - // for simplicity, only support single protocol services (simulating - // multiple protocols on the same peer is extra effort, and we don't - // currently run any simulations which run multiple protocols) - if len(service.Protocols()) != 1 { - return errors.New("service must have a single protocol") +// Start starts the RPC handler and the underlying service +func (self *SimNode) Start(snapshot []byte) error { + self.lock.Lock() + defer self.lock.Unlock() + if self.node != nil { + return errors.New("node already started") + } + + services := []node.ServiceConstructor{} + + sf := self.serviceFunc(self.Id, snapshot) + + for i, _ := range sf { + service := sf[i] + sc := func(ctx *node.ServiceContext) (node.Service, error) { + return service, nil + } + log.Debug(fmt.Sprintf("servicefunc yield: %v %p %p", reflect.TypeOf(sf[i]), sf[i], sc)) + services = append(services, sc) + self.running = append(self.running, sf[i]) } - self.dropPeers = make(chan struct{}) - if err := self.startRPC(service); err != nil { + node, err := node.New(&node.Config{ + P2P: p2p.Config{ + PrivateKey: self.config.PrivateKey, + MaxPeers: math.MaxInt32, + NoDiscovery: true, + Dialer: self.adapter, + EnableMsgEvents: false, + }, + NoUSB: true, + }) + if err != nil { return err } - self.running = service - return service.Start(&simServer{self}) -} - -// simServer wraps a SimNode but modifies the Start method signature so that -// it implements the p2p.Server interface (the Start method is never actually -// called when using the SimAdapter) -type simServer struct { - *SimNode -} - -func (s *simServer) Start() error { - return nil -} - -// Stop stops the RPC handler, stops the underlying service and disconnects -// any currently connected peers -func (self *SimNode) Stop() error { - self.stopRPC() - close(self.dropPeers) - return self.running.Stop() -} - -// Running returns whether or not the service is running -func (self *SimNode) Running() bool { - self.lock.Lock() - defer self.lock.Unlock() - return self.running != nil -} - -// Service returns the running node.Service -func (self *SimNode) Service() node.Service { - return self.running -} - -// startRPC starts an RPC server and connects to it using an in-process RPC -// client -func (self *SimNode) startRPC(service node.Service) error { - self.lock.Lock() - defer self.lock.Unlock() - if self.client != nil { - return errors.New("RPC already started") + + for _, service := range services { + log.Debug(fmt.Sprintf("service %v", service)) + if err := node.Register(service); err != nil { + return err + } } - // add SimAdminAPI so that the network can call the - // AddPeer, RemovePeer and PeerEvents RPC methods - apis := append(service.APIs(), []rpc.API{ - { - Namespace: "admin", - Version: "1.0", - Service: &SimAdminAPI{self}, - }, - }...) + if err := node.Start(); err != nil { + return err + } - // start the RPC handler - handler := rpc.NewServer() - for _, api := range apis { - if err := handler.RegisterName(api.Namespace, api.Service); err != nil { - return fmt.Errorf("error registering RPC: %s", err) - } + handler, err := node.RPCHandler() + if err != nil { + return err } // create an in-process RPC multiplexer @@ -240,197 +263,61 @@ func (self *SimNode) startRPC(service node.Service) error { // create an in-process RPC client self.client = self.rpcMux.Client() + self.node = node + return nil } -// stopRPC closes the node's RPC client -func (self *SimNode) stopRPC() { +func (self *SimNode) Stop() error { self.lock.Lock() defer self.lock.Unlock() - if self.client != nil { - self.client.Close() - self.client = nil - self.rpcMux = nil + if self.node == nil { + return nil } + if err := self.node.Stop(); err != nil { + return err + } + self.node = nil + return nil } -// RemovePeer removes the given node as a peer by looking up the corresponding -// p2p.MsgReadWriter pipe and closing it (which will cause both the local -// and peer Protocol.Run functions to exit) -func (self *SimNode) RemovePeer(peer *discover.Node) { +// Service returns the underlying running node.Service matching the supplied servuce type +func (self *SimNode) Service(servicetype interface{}) node.Service { self.lock.Lock() defer self.lock.Unlock() - peerRW, exists := self.peers[peer.ID] - if !exists { - return + typ := reflect.TypeOf(servicetype) + for _, service := range self.running { + if reflect.TypeOf(service) == typ { + return service + } } - peerRW.Close() - delete(self.peers, peer.ID) - log.Trace(fmt.Sprintf("dropped peer %v", peer.ID)) + return nil } -// AddPeer adds the given node as a peer by creating a p2p.MsgReadWriter pipe -// and running both the local and peer's Protocol.Run function over the pipe -func (self *SimNode) AddPeer(peer *discover.Node) { +func (self *SimNode) Server() *p2p.Server { self.lock.Lock() defer self.lock.Unlock() - if _, exists := self.peers[peer.ID]; exists { - return + if self.node == nil { + return nil } - peerNode, exists := self.adapter.GetNode(peer.ID) - if !exists { - panic(fmt.Sprintf("unknown peer: %s", peer.ID)) - } - if !peerNode.Running() { - return - } - p1, p2 := p2p.MsgPipe() - localRW := p2p.NewMsgEventer(p1, &self.peerFeed, peer.ID) - peerRW := p2p.NewMsgEventer(p2, &self.peerFeed, self.Id.NodeID) - self.peers[peer.ID] = peerRW - peerNode.RunProtocol(self, peerRW) - self.RunProtocol(peerNode, localRW) + return self.node.Server() } -// SubscribeEvents subscribes the given channel to p2p peer events func (self *SimNode) SubscribeEvents(ch chan *p2p.PeerEvent) event.Subscription { - return self.peerFeed.Subscribe(ch) + srv := self.Server() + if srv == nil { + panic("node not running") + } + return srv.SubscribeEvents(ch) } -// PeerCount returns the number of currently connected peers -func (self *SimNode) PeerCount() int { - self.lock.Lock() - defer self.lock.Unlock() - return len(self.peers) -} - -// NodeInfo returns information about the node func (self *SimNode) NodeInfo() *p2p.NodeInfo { - self.lock.Lock() - defer self.lock.Unlock() - info := &p2p.NodeInfo{ - ID: self.Id.String(), - Enode: self.Node().String(), - Protocols: make(map[string]interface{}), - } - if self.running != nil { - for _, proto := range self.running.Protocols() { - nodeInfo := interface{}("unknown") - if query := proto.NodeInfo; query != nil { - nodeInfo = proto.NodeInfo() - } - info.Protocols[proto.Name] = nodeInfo + server := self.Server() + if server == nil { + return &p2p.NodeInfo{ + ID: self.Id.String(), + Enode: self.Node().String(), } } - return info -} - -// PeersInfo is a stub so that SimNode implements p2p.Server -func (self *SimNode) PeersInfo() (info []*p2p.PeerInfo) { - return nil -} - -// Snapshot creates a snapshot of the running service -func (self *SimNode) Snapshot() ([]byte, error) { - self.lock.Lock() - service := self.running - self.lock.Unlock() - if service == nil { - return nil, errors.New("service not running") - } - return SnapshotAPI{service}.Snapshot() -} - -// RunProtocol runs the underlying service's protocol with the peer using the -// given MsgReadWriteCloser, emitting peer add / drop events for peer event -// subscribers -func (self *SimNode) RunProtocol(peer *SimNode, rw MsgReadWriteCloser) { - // close the rw if the node is stopped to disconnect the peer - go func() { - <-self.dropPeers - log.Trace("dropping peer", "self.id", self.Id, "peer.id", peer.Id) - rw.Close() - }() - - id := peer.Id - log.Trace(fmt.Sprintf("protocol starting on peer %v (connection with %v)", self.Id, id)) - protocol := self.running.Protocols()[0] - p := p2p.NewPeer(id.NodeID, id.Label(), []p2p.Cap{}) - go func() { - // emit peer add event - self.peerFeed.Send(&p2p.PeerEvent{ - Type: p2p.PeerEventTypeAdd, - Peer: id.NodeID, - }) - - // run the protocol - err := protocol.Run(p, rw) - - // remove the peer - self.RemovePeer(peer.Node()) - log.Trace(fmt.Sprintf("protocol quit on peer %v (connection with %v broken: %v)", self.Id, id, err)) - - // emit peer drop event - self.peerFeed.Send(&p2p.PeerEvent{ - Type: p2p.PeerEventTypeDrop, - Peer: id.NodeID, - Error: err.Error(), - }) - }() -} - -// SimAdminAPI implements the AddPeer and RemovePeer RPC methods (API -// compatible with node.PrivateAdminAPI) -type SimAdminAPI struct { - *SimNode -} - -func (api *SimAdminAPI) AddPeer(url string) (bool, error) { - node, err := discover.ParseNode(url) - if err != nil { - return false, fmt.Errorf("invalid enode: %v", err) - } - api.SimNode.AddPeer(node) - return true, nil -} - -func (api *SimAdminAPI) RemovePeer(url string) (bool, error) { - node, err := discover.ParseNode(url) - if err != nil { - return false, fmt.Errorf("invalid enode: %v", err) - } - api.SimNode.RemovePeer(node) - return true, nil -} - -// PeerEvents creates an RPC subscription which receives peer events from the -// underlying p2p.Server -func (api *SimAdminAPI) 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 := api.SubscribeEvents(events) - defer sub.Unsubscribe() - - for { - select { - case event := <-events: - notifier.Notify(rpcSub.ID, event) - case <-sub.Err(): - return - case <-rpcSub.Err(): - return - case <-notifier.Closed(): - return - } - } - }() - - return rpcSub, nil + return server.NodeInfo() } diff --git a/p2p/simulations/adapters/state.go b/p2p/simulations/adapters/state.go new file mode 100644 index 0000000000..2d7882c86b --- /dev/null +++ b/p2p/simulations/adapters/state.go @@ -0,0 +1,20 @@ +package adapters + +type SimStateStore struct { + m map[string][]byte +} + +func (self *SimStateStore) Load(s string) ([]byte, error) { + return self.m[s], nil +} + +func (self *SimStateStore) Save(s string, data []byte) error { + self.m[s] = data + return nil +} + +func NewSimStateStore() *SimStateStore { + return &SimStateStore{ + make(map[string][]byte), + } +} diff --git a/p2p/simulations/adapters/types.go b/p2p/simulations/adapters/types.go index e7f15ba8f7..c418cb7e32 100644 --- a/p2p/simulations/adapters/types.go +++ b/p2p/simulations/adapters/types.go @@ -127,9 +127,9 @@ type NodeConfig struct { // Name is a human friendly name for the node like "node01" Name string - // Service is the name of the service which should be run when starting - // the node (for SimNodes it should be the name of a service contained - // in SimAdapter.services, for other nodes it should be a service + // Service is the name 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) Service string } @@ -140,7 +140,7 @@ type nodeConfigJSON struct { Id string `json:"id"` PrivateKey string `json:"private_key"` Name string `json:"name"` - Service string `json:"service"` + Service string `json:"service"` } func (n *NodeConfig) MarshalJSON() ([]byte, error) { @@ -202,10 +202,11 @@ func RandomNodeConfig() *NodeConfig { } // Services is a collection of services which can be run in a simulation +// it is mapped to strings representing TYPES of nodes type Services map[string]ServiceFunc // ServiceFunc returns a node.Service which can be used to boot devp2p nodes -type ServiceFunc func(id *NodeId, snapshot []byte) node.Service +type ServiceFunc func(id *NodeId, snapshot []byte) []node.Service // serviceFuncs is a map of registered services which are used to boot devp2p // nodes diff --git a/p2p/simulations/examples/connectivity.go b/p2p/simulations/examples/connectivity.go index 43beff951a..472054cb7a 100644 --- a/p2p/simulations/examples/connectivity.go +++ b/p2p/simulations/examples/connectivity.go @@ -99,7 +99,7 @@ func (p *pingPongService) APIs() []rpc.API { return nil } -func (p *pingPongService) Start(server p2p.Server) error { +func (p *pingPongService) Start(server *p2p.Server) error { p.log.Info("ping-pong service starting") return nil } diff --git a/p2p/simulations/http.go b/p2p/simulations/http.go index acc3888b1a..a0f30c7460 100644 --- a/p2p/simulations/http.go +++ b/p2p/simulations/http.go @@ -59,6 +59,21 @@ func (c *Client) GetNetwork(networkID string) (*Network, error) { return network, c.Get(fmt.Sprintf("/networks/%s", networkID), network) } +// StartNetwork starts all existing nodes in a simulation network +func (c *Client) StartNetwork(networkID string) error { + return c.Post(fmt.Sprintf("/networks/%s/start", networkID), nil, nil) +} + +// StopNetwork stops all existing nodes in a simulation network +func (c *Client) StopNetwork(networkID string) error { + return c.Post(fmt.Sprintf("/networks/%s/stop", networkID), nil, nil) +} + +// DeleteNetwork stops and deletes a simulation network +func (c *Client) DeleteNetwork(networkID string) error { + return c.Delete(fmt.Sprintf("/networks/%s", networkID)) +} + // CreateSnapshot creates a network snapshot func (c *Client) CreateSnapshot(networkID string) (*Snapshot, error) { snap := &Snapshot{} @@ -276,6 +291,9 @@ func NewServer(config *ServerConfig) *Server { s.POST("/networks", s.CreateNetwork) s.GET("/networks", s.GetNetworks) s.GET("/networks/:netid", s.GetNetwork) + s.POST("/networks/:netid/start", s.StartNetwork) + s.POST("/networks/:netid/stop", s.StopNetwork) + s.DELETE("/networks/:netid", s.DeleteNetwork) s.GET("/networks/:netid/events", s.StreamNetworkEvents) s.GET("/networks/:netid/snapshot", s.CreateSnapshot) s.POST("/networks/:netid/snapshot", s.LoadSnapshot) @@ -341,6 +359,46 @@ func (s *Server) GetNetwork(w http.ResponseWriter, req *http.Request) { s.JSON(w, http.StatusOK, network) } +// StartNetwork starts all nodes in a network +func (s *Server) StartNetwork(w http.ResponseWriter, req *http.Request) { + network := req.Context().Value("network").(*Network) + + if err := network.StartAll(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) +} + +// StopNetwork stops all nodes in a network +func (s *Server) StopNetwork(w http.ResponseWriter, req *http.Request) { + network := req.Context().Value("network").(*Network) + + if err := network.StopAll(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) +} + +// DeleteNetwork stops all nodes in a network and deletes it +func (s *Server) DeleteNetwork(w http.ResponseWriter, req *http.Request) { + network := req.Context().Value("network").(*Network) + + if err := network.StopAll(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + s.mtx.Lock() + delete(s.networks, network.Id) + s.mtx.Unlock() + + w.WriteHeader(http.StatusOK) +} + //Get the info for a particular mocker func (s *Server) GetMocker(w http.ResponseWriter, req *http.Request) { m := make(map[string]string) diff --git a/p2p/simulations/http_test.go b/p2p/simulations/http_test.go index f2c31d48e7..99fb49ed8f 100644 --- a/p2p/simulations/http_test.go +++ b/p2p/simulations/http_test.go @@ -46,7 +46,7 @@ func (t *testService) APIs() []rpc.API { }} } -func (t *testService) Start(server p2p.Server) error { +func (t *testService) Start(server *p2p.Server) error { return nil } diff --git a/p2p/simulations/network.go b/p2p/simulations/network.go index 0adc696ca8..77cd4b9cbd 100644 --- a/p2p/simulations/network.go +++ b/p2p/simulations/network.go @@ -260,6 +260,30 @@ func (self *Conn) nodesUp() error { return nil } +func (self *Network) StartAll() error { + for _, node := range self.Nodes { + if node.Up { + continue + } + if err := self.Start(node.ID()); err != nil { + return err + } + } + return nil +} + +func (self *Network) StopAll() error { + for _, node := range self.Nodes { + if !node.Up { + continue + } + if err := self.Stop(node.ID()); err != nil { + return err + } + } + return nil +} + // Start(id) starts up the node (relevant only for instance with own p2p or remote) func (self *Network) Start(id *adapters.NodeId) error { return self.startWithSnapshot(id, nil) @@ -275,6 +299,7 @@ func (self *Network) startWithSnapshot(id *adapters.NodeId, snapshot []byte) err } log.Trace(fmt.Sprintf("starting node %v: %v using %v", id, node.Up, self.nodeAdapter.Name())) if err := node.Start(snapshot); err != nil { + log.Warn(fmt.Sprintf("start up failed: %v", err)) return err } node.Up = true diff --git a/p2p/testing/protocolsession.go b/p2p/testing/protocolsession.go index 1dfbb6ab49..eda8441136 100644 --- a/p2p/testing/protocolsession.go +++ b/p2p/testing/protocolsession.go @@ -13,8 +13,7 @@ import ( ) type ProtocolSession struct { - *adapters.SimNode - + Server *p2p.Server Ids []*adapters.NodeId adapter *adapters.SimAdapter events chan *p2p.PeerEvent @@ -58,7 +57,7 @@ func (self *ProtocolSession) trigger(trig Trigger) error { if !ok { return fmt.Errorf("trigger: peer %v does not exist (1- %v)", trig.Peer, len(self.Ids)) } - mockNode, ok := simNode.Service().(*mockNode) + mockNode, ok := simNode.Service(&mockNode{}).(*mockNode) if !ok { return fmt.Errorf("trigger: peer %v is not a mock", trig.Peer) } @@ -93,7 +92,7 @@ func (self *ProtocolSession) expect(exp Expect) error { if !ok { return fmt.Errorf("trigger: peer %v does not exist (1- %v)", exp.Peer, len(self.Ids)) } - mockNode, ok := simNode.Service().(*mockNode) + mockNode, ok := simNode.Service(&mockNode{}).(*mockNode) if !ok { return fmt.Errorf("trigger: peer %v is not a mock", exp.Peer) } diff --git a/p2p/testing/protocoltester.go b/p2p/testing/protocoltester.go index 16a23f120f..a328cb06d7 100644 --- a/p2p/testing/protocoltester.go +++ b/p2p/testing/protocoltester.go @@ -19,14 +19,16 @@ type ProtocolTester struct { } func NewProtocolTester(t *testing.T, id *adapters.NodeId, n int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester { - services := map[string]adapters.ServiceFunc{ - "test": func(id *adapters.NodeId) node.Service { - return &testNode{run} +//func NewProtocolTester(t *testing.T, id *adapters.NodeId, n int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester { + services := adapters.Services { + "test": func(id *adapters.NodeId, _ []byte) []node.Service { + return []node.Service{&testNode{run}} }, - "mock": func(id *adapters.NodeId) node.Service { - return newMockNode() + "mock": func(id *adapters.NodeId, _ []byte) []node.Service { + return []node.Service{newMockNode()} }, } + adapters.RegisterServices(services) adapter := adapters.NewSimAdapter(services) net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{}) if _, err := net.NewNodeWithConfig(&adapters.NodeConfig{Id: id, Service: "test"}); err != nil { @@ -47,7 +49,7 @@ func NewProtocolTester(t *testing.T, id *adapters.NodeId, n int, run func(*p2p.P events := make(chan *p2p.PeerEvent, 1000) node.SubscribeEvents(events) ps := &ProtocolSession{ - SimNode: node, + Server: node.Server(), Ids: peerIDs, adapter: adapter, events: events, @@ -62,6 +64,10 @@ func NewProtocolTester(t *testing.T, id *adapters.NodeId, n int, run func(*p2p.P return self } +func (self *ProtocolTester) Stop() error { + return self.Server.Stop() +} + func (self *ProtocolTester) Connect(selfId *adapters.NodeId, peers ...*adapters.NodeConfig) { for _, peer := range peers { log.Trace(fmt.Sprintf("start node %v", peer.Id)) @@ -86,14 +92,17 @@ type testNode struct { } func (t *testNode) Protocols() []p2p.Protocol { - return []p2p.Protocol{{Run: t.run}} + return []p2p.Protocol{{ + Length: 100, + Run: t.run, + }} } func (t *testNode) APIs() []rpc.API { return nil } -func (t *testNode) Start(server p2p.Server) error { +func (t *testNode) Start(server *p2p.Server) error { return nil } diff --git a/pot/address.go b/pot/address.go index 4e4a76d03e..05f2f82167 100644 --- a/pot/address.go +++ b/pot/address.go @@ -287,3 +287,62 @@ func (self *BoolAddress) PO(val PotVal, pos int) (po int, eq bool) { } return po, true } + +type BytesAddress interface { + Address() []byte +} + +type bytesAddress struct { + bytes []byte + toBytes func(v AnyVal) []byte +} + +func NewBytesVal(v AnyVal, f func(v AnyVal) []byte) *bytesAddress { + if f == nil { + f = ToBytes + } + b := f(v) + return &bytesAddress{b, f} +} + +func ToBytes(v AnyVal) []byte { + b, ok := v.([]byte) + if !ok { + ba, ok := v.(BytesAddress) + if !ok { + panic(fmt.Sprintf("unsupported value type %T", v)) + } + b = ba.Address() + } + return b +} + +func (a *bytesAddress) String() string { + return fmt.Sprintf("%08b", a.bytes) +} +func (a *bytesAddress) Address() []byte { + return a.bytes +} + +func (a *bytesAddress) PO(val PotVal, i int) (int, bool) { + return proximityOrder(a.bytes, a.toBytes(val), i) +} + +func proximityOrder(one, other []byte, pos int) (int, bool) { + for i := pos / 8; i < len(one); i++ { + if one[i] == other[i] { + continue + } + oxo := one[i] ^ other[i] + start := 0 + if i == pos/8 { + start = pos % 8 + } + for j := start; j < 8; j++ { + if (uint8(oxo)>>uint8(7-j))&0x01 != 0 { + return i*8 + j, false + } + } + } + return len(one) * 8, true +} diff --git a/pot/pot.go b/pot/pot.go index bb1d4c936e..eef5037b4e 100644 --- a/pot/pot.go +++ b/pot/pot.go @@ -45,6 +45,8 @@ type PotVal interface { String() string } +type AnyVal interface{} + // Pot constructor. Requires value of type PotVal to pin // and po to point to a span in the PotVal key // The pinned item counts towards the size @@ -245,11 +247,12 @@ func remove(t *pot, val PotVal) (r *pot, po int, found bool) { // if f returns v' <> v then v' is inserted into the Pot // if v' == v the pot is not changed // it panics if v'.PO(k, 0) says v and k are not equal -func (t *Pot) Swap(val PotVal, f func(v PotVal) PotVal) (po int, found bool, change bool) { +func (t *Pot) Swap(val AnyVal, f func(v PotVal) PotVal) (po int, found bool, change bool) { t.lock.Lock() defer t.lock.Unlock() + ba := NewBytesVal(val, nil) var t0 *pot - t0, po, found, change = swap(t.pot, val, f) + t0, po, found, change = swap(t.pot, ba, f) if change { t.pot = t0 } diff --git a/swarm/network/discovery.go b/swarm/network/discovery.go index 91ad122c6b..e6492e2b35 100644 --- a/swarm/network/discovery.go +++ b/swarm/network/discovery.go @@ -8,59 +8,62 @@ import ( // discovery bzz overlay extension doing peer relaying -// messages related to peer discovery -var DiscoveryMsgs = []interface{}{ - &getPeersMsg{}, - &peersMsg{}, - &subPeersMsg{}, -} - type discPeer struct { - Peer + *bzzPeer overlay Overlay peers map[string]bool - proxLimit uint8 // the proximity radius advertised by remote to subscribe to peers + depth uint8 // the proximity radius advertised by remote to subscribe to peers sentPeers bool // set to true when the peer is first notifed of peers close to them } -// discovery peer contructor -// registers the handlers for discovery messages -func NewDiscovery(p Peer, o Overlay) *discPeer { +// NewDiscovery discovery peer contructor +func NewDiscovery(p *bzzPeer, o Overlay) *discPeer { self := &discPeer{ overlay: o, - Peer: p, + bzzPeer: p, peers: make(map[string]bool), } self.seen(self) - - p.Register(&peersMsg{}, self.handlePeersMsg) - p.Register(&getPeersMsg{}, self.handleGetPeersMsg) - p.Register(&subPeersMsg{}, self.handleSubPeersMsg) - return self } +func (self *discPeer) HandleMsg(msg interface{}) error { + switch msg := msg.(type) { + + case *peersMsg: + return self.handlePeersMsg(msg) + + case *getPeersMsg: + return self.handleGetPeersMsg(msg) + + case *subPeersMsg: + return self.handleSubPeersMsg(msg) + + default: + return fmt.Errorf("unknown message type: %T", msg) + } +} + // NotifyPeer notifies the receiver remote end of a peer p or PO po. // callback for overlay driver -func (self *discPeer) NotifyPeer(p Peer, po uint8) error { - log.Warn(fmt.Sprintf("peer %#v peers %v", p, self.peers)) - if po < self.proxLimit || self.seen(p) { +func (self *discPeer) NotifyPeer(a OverlayAddr, po uint8) error { + if po < self.depth || self.seen(a) { return nil } - log.Warn(fmt.Sprintf("notification about %x", p.OverlayAddr())) + log.Warn(fmt.Sprintf("notification about %x", a.Address())) resp := &peersMsg{ - Peers: []*peerAddr{&peerAddr{OAddr: p.OverlayAddr(), UAddr: p.UnderlayAddr()}}, // perhaps the PeerAddr interface is unnecessary generalization + Peers: []*bzzAddr{ToAddr(a)}, // perhaps the PeerAddr interface is unnecessary generalization } return self.Send(resp) } -// NotifyProx sends a subPeers Msg to the receiver notifying them about +// NotifyDepth sends a subPeers Msg to the receiver notifying them about // a change in the prox limit (radius of the set including the nearest X peers // or first empty row) // callback for overlay driver -func (self *discPeer) NotifyProx(po uint8) error { - return self.Send(&subPeersMsg{ProxLimit: po}) +func (self *discPeer) NotifyDepth(po uint8) error { + return self.Send(&subPeersMsg{Depth: po}) } /* @@ -83,7 +86,7 @@ disconnected // used for communicating about known peers // relevant for bootstrapping connectivity and updating peersets type peersMsg struct { - Peers []*peerAddr + Peers []*bzzAddr } func (self peersMsg) String() string { @@ -102,28 +105,27 @@ func (self getPeersMsg) String() string { // subPeers msg is communicating the depth/sharpness/focus of the overlay table of a peer type subPeersMsg struct { - ProxLimit uint8 + Depth uint8 } func (self subPeersMsg) String() string { - return fmt.Sprintf("%T: request peers > PO%02d. ", self, self.ProxLimit) + return fmt.Sprintf("%T: request peers > PO%02d. ", self, self.Depth) } -func (self *discPeer) handleSubPeersMsg(msg interface{}) error { - spm := msg.(*subPeersMsg) - self.proxLimit = spm.ProxLimit +func (self *discPeer) handleSubPeersMsg(msg *subPeersMsg) error { + self.depth = msg.Depth if !self.sentPeers { - var peers []*peerAddr - self.overlay.EachLivePeer(self.OverlayAddr(), 255, func(p Peer, po int, isproxbin bool) bool { - if uint8(po) < self.proxLimit { + var peers []*bzzAddr + self.overlay.EachConn(self.Over(), 255, func(p OverlayConn, po int, isproxbin bool) bool { + if uint8(po) < self.depth { return false } - log.Warn(fmt.Sprintf("peer %#v proxlimit %v", p, self.proxLimit)) - self.seen(p.(*discPeer).Peer) - peers = append(peers, &peerAddr{p.OverlayAddr(), p.UnderlayAddr()}) + if !self.seen(p) { + peers = append(peers, ToAddr(p.Off())) + } return true }) - log.Warn(fmt.Sprintf("found initial %v peers not farther than %v", len(peers), self.proxLimit)) + log.Warn(fmt.Sprintf("found initial %v peers not farther than %v", len(peers), self.depth)) if len(peers) > 0 { if err := self.Send(&peersMsg{Peers: peers}); err != nil { return err @@ -137,39 +139,41 @@ func (self *discPeer) handleSubPeersMsg(msg interface{}) error { // handlePeersMsg called by the protocol when receiving peerset (for target address) // list of nodes ([]PeerAddr in peersMsg) is added to the overlay db using the // Register interface method -func (self *discPeer) handlePeersMsg(msg interface{}) error { +func (self *discPeer) handlePeersMsg(msg *peersMsg) error { // register all addresses - var nas []PeerAddr - for _, na := range msg.(*peersMsg).Peers { - addr := PeerAddr(na) - nas = append(nas, addr) - self.seen(addr) - } - - if len(nas) == 0 { + if len(msg.Peers) == 0 { log.Debug(fmt.Sprintf("whoops, no peers in incoming peersMsg from %v", self)) return nil } - log.Debug(fmt.Sprintf("got peer addresses from %x, %v (%v)", self.OverlayAddr(), nas, len(nas))) - return self.overlay.Register(nas...) + + c := make(chan OverlayAddr) + go func() { + defer close(c) + for _, a := range msg.Peers { + self.seen(a) + c <- a + } + }() + log.Info("discovery overlay register") + return self.overlay.Register(c) } // handleGetPeersMsg is called by the protocol when receiving a // peerset (for target address) request // peers suggestions are retrieved from the overlay topology driver -// using the EachLivePeer interface iterator method +// using the EachConn interface iterator method // peers sent are remembered throughout a session and not sent twice -func (self *discPeer) handleGetPeersMsg(msg interface{}) error { - var peers []*peerAddr - req := msg.(*getPeersMsg) +func (self *discPeer) handleGetPeersMsg(msg *getPeersMsg) error { + var peers []*bzzAddr i := 0 - self.overlay.EachLivePeer(self.OverlayAddr(), int(req.Order), func(n Peer, po int, isproxbin bool) bool { + self.overlay.EachConn(self.Over(), int(msg.Order), func(p OverlayConn, po int, isproxbin bool) bool { i++ // only send peers we have not sent before in this session - if self.seen(n) { - peers = append(peers, &peerAddr{n.OverlayAddr(), n.UnderlayAddr()}) + a := ToAddr(p.Off()) + if self.seen(a) { + peers = append(peers, a) } - return len(peers) < int(req.Max) + return len(peers) < int(msg.Max) }) if len(peers) == 0 { log.Debug(fmt.Sprintf("no peers found for %v", self)) @@ -189,9 +193,8 @@ func RequestOrder(k Overlay, order, broadcastSize, maxPeers uint8) { } var i uint8 //var err error - k.EachLivePeer(nil, 255, func(n Peer, po int, isproxbin bool) bool { - log.Trace(fmt.Sprintf("%T sent to %v", req, n)) - if err := n.Send(req); err == nil { + k.EachConn(nil, 255, func(p OverlayConn, po int, isproxbin bool) bool { + if err := p.(Conn).Send(req); err == nil { i++ if i >= broadcastSize { return false @@ -202,8 +205,8 @@ func RequestOrder(k Overlay, order, broadcastSize, maxPeers uint8) { log.Info(fmt.Sprintf("requesting bees of PO%03d from %v/%v (each max %v)", order, i, broadcastSize, maxPeers)) } -func (self *discPeer) seen(p PeerAddr) bool { - k := NodeId(p).NodeID.String() +func (self *discPeer) seen(p OverlayPeer) bool { + k := string(p.Address()) if self.peers[k] { return true } diff --git a/swarm/network/discovery_test.go b/swarm/network/discovery_test.go index d737b3e04f..274d046740 100644 --- a/swarm/network/discovery_test.go +++ b/swarm/network/discovery_test.go @@ -16,29 +16,24 @@ import ( func TestDiscovery(t *testing.T) { addr := RandomAddr() to := NewKademlia(addr.OAddr, NewKadParams()) - ct := BzzCodeMap(DiscoveryMsgs...) - services := func(p Peer) error { + run := func(p *bzzPeer) error { dp := NewDiscovery(p, to) - to.On(dp) + to.On(p) + defer to.Off(p) log.Trace(fmt.Sprintf("kademlia on %v", p)) - p.DisconnectHook(func(err error) { - to.Off(p) - }) - return nil + return p.Run(dp.HandleMsg) } - s := newBzzBaseTester(t, 1, addr, ct, services) + s := newBzzBaseTester(t, 1, addr, DiscoverySpec, run) defer s.Stop() - s.runHandshakes() - // o := 0 s.TestExchanges(p2ptest.Exchange{ Label: "outgoing SubPeersMsg", Expects: []p2ptest.Expect{ p2ptest.Expect{ Code: 3, - Msg: &subPeersMsg{ProxLimit: 0}, + Msg: &subPeersMsg{Depth: 0}, Peer: s.ProtocolTester.Ids[0], }, }, diff --git a/swarm/network/hive.go b/swarm/network/hive.go index 61642a1532..f9a437868f 100644 --- a/swarm/network/hive.go +++ b/swarm/network/hive.go @@ -17,6 +17,7 @@ package network import ( + "encoding/json" "fmt" "sync" "time" @@ -33,75 +34,89 @@ it uses an Overlay Topology driver (e.g., generic kademlia nodetable) to find best peer list for any target this is used by the netstore to search for content in the swarm -It handles the bzz protocol getPeersMsg peersMsg exchange +It handles the hive protocol getPeersMsg peersMsg exchange and relay the peer request process to the Overlay module peer connections and disconnections are reported and registered to keep the nodetable uptodate */ + +// Overlay is the interface to Jaak ahd ka)a type Overlay interface { - Register(...PeerAddr) error + Register(chan OverlayAddr) error - On(Peer) - Off(Peer) + On(OverlayConn) + Off(OverlayConn) - EachLivePeer([]byte, int, func(Peer, int, bool) bool) - EachPeer([]byte, int, func(PeerAddr, int) bool) + EachConn([]byte, int, func(OverlayConn, int, bool) bool) + EachAddr([]byte, int, func(OverlayAddr, int) bool) - SuggestPeer() (PeerAddr, int, bool) + SuggestPeer() (OverlayAddr, int, bool) String() string - GetAddr() PeerAddr + BaseAddr() []byte + Healthy([][]byte) bool +} + +// HiveParams holds the config options to hive +type HiveParams struct { + Discovery bool // if want discovery of not + PeersBroadcastSetSize uint8 // how many peers to use when relaying + MaxPeersPerRequest uint8 // max size for peer address batches + KeepAliveInterval time.Duration +} + +// NewHiveParams returns hive config with only the +func NewHiveParams() *HiveParams { + return &HiveParams{ + Discovery: true, + PeersBroadcastSetSize: 2, + MaxPeersPerRequest: 5, + KeepAliveInterval: time.Second, + } } // Hive implements the PeerPool interface type Hive struct { *HiveParams // settings Overlay // the overlay topology driver - lock sync.Mutex - quit chan bool - toggle chan bool - more chan bool -} + store StateStore -type HiveParams struct { - Discovery bool - PeersBroadcastSetSize uint8 - MaxPeersPerRequest uint8 - CallInterval uint -} + // bookkeeping + lock sync.Mutex + quit chan bool + more chan bool -func NewHiveParams() *HiveParams { - return &HiveParams{ - Discovery: true, - PeersBroadcastSetSize: 2, - MaxPeersPerRequest: 5, - CallInterval: 1000, - } + tick <-chan time.Time } // Hive constructor embeds both arguments -// HiveParams config parameters -// Overlay Topology Driver Interface -func NewHive(params *HiveParams, overlay Overlay) *Hive { +// HiveParams: config parameters +// Overlay: Topology Driver Interface +func NewHive(params *HiveParams, overlay Overlay, store StateStore) *Hive { return &Hive{ HiveParams: params, Overlay: overlay, + store: store, } } // Start receives network info only at startup -// connectPeer is a function to connect to a peer based on its NodeID or enode URL +// server is used to connect to a peer based on its NodeID or enode URL // these are called on the p2p.Server which runs on the node // af() returns an arbitrary ticker channel -func (self *Hive) Start(server p2p.Server, af func() <-chan time.Time) error { - - self.toggle = make(chan bool) +// rw is a read writer for json configs +func (self *Hive) Start(server *p2p.Server) error { + if self.store != nil { + if err := self.loadPeers(); err != nil { + return err + } + } self.more = make(chan bool, 1) self.quit = make(chan bool) log.Debug("hive started") // this loop is doing bootstrapping and maintains a healthy table - go self.keepAlive(af) + go self.keepAlive() go func() { // each iteration, ask kademlia about most preferred peer for more := range self.more { @@ -110,14 +125,15 @@ func (self *Hive) Start(server p2p.Server, af func() <-chan time.Time) error { // to attempt to write to more (remove Peer when shutting down) return } - log.Trace("hive delegate to overlay driver: suggest addr to connect to") + log.Debug("hive delegate to overlay driver: suggest addr to connect to") + // log.Trace("hive delegate to overlay driver: suggest addr to connect to") addr, order, want := self.SuggestPeer() if addr != nil { log.Info(fmt.Sprintf("========> connect to bee %v", addr)) - node, err := discover.ParseNode(NodeId(addr).NodeID.String()) + under, err := discover.ParseNode(string(addr.(Addr).Under())) if err == nil { - server.AddPeer(node) + server.AddPeer(under) } else { log.Error(fmt.Sprintf("===X====> connect to bee %v failed: invalid node URL: %v", addr, err)) } @@ -127,75 +143,44 @@ func (self *Hive) Start(server p2p.Server, af func() <-chan time.Time) error { want = want && self.Discovery if want { - go RequestOrder(self.Overlay, uint8(order), self.PeersBroadcastSetSize, self.MaxPeersPerRequest) + log.Debug(fmt.Sprintf("========> request peers nearest %v", addr)) + RequestOrder(self.Overlay, uint8(order), self.PeersBroadcastSetSize, self.MaxPeersPerRequest) } + log.Info(fmt.Sprintf("%v", self)) select { - case self.toggle <- want: - log.Trace(fmt.Sprintf("keep hive alive: %v", want)) case <-self.quit: return + default: } - log.Info(fmt.Sprintf("%v", self)) } }() return nil } -func (self *Hive) ticker() <-chan time.Time { - return time.NewTicker(time.Duration(self.CallInterval) * time.Millisecond).C -} - -// keepAlive is a forever loop -// in its awake state it periodically triggers connection attempts -// by writing to self.more until Kademlia Table is saturated -// wake state is toggled by writing to self.toggle -// it restarts if the table becomes non-full again due to disconnections -func (self *Hive) keepAlive(af func() <-chan time.Time) { - log.Trace("keep alive loop started") - alarm := af() - for { - select { - case <-alarm: - log.Trace("wake up: make hive alive") - self.wake() - case need := <-self.toggle: - if alarm == nil && need { - alarm = af() - } - // if hive saturated, no more peers asked - if alarm != nil && !need { - alarm = nil - } - case <-self.quit: - return - } +// Stop terminates the updateloop and saves the peers +func (self *Hive) Stop() { + if self.store != nil { + self.savePeers() } + // closing toggle channel quits the updateloop + close(self.quit) } -// Add is called at the end of a successful protocol handshake -// to register a connected (live) peer -func (self *Hive) Add(p Peer) error { - defer self.wake() - dp := NewDiscovery(p, self.Overlay) +func (self *Hive) Run(p *bzzPeer) error { + dp := NewDiscovery(p, self) log.Debug(fmt.Sprintf("to add new bee %v", p)) self.On(dp) - self.String() - log.Debug(fmt.Sprintf("%v", self)) - return nil -} - -// Remove called after peer is disconnected -func (self *Hive) Remove(p Peer) { + self.wake() defer self.wake() - log.Debug(fmt.Sprintf("remove bee %v", p)) - self.Off(p) + defer self.Off(dp) + return p.Run(dp.HandleMsg) } // NodeInfo function is used by the p2p.server RPC interface to display // protocol specific node information func (self *Hive) NodeInfo() interface{} { - return interface{}(self.String()) + return self.String() } // PeerInfo function is used by the p2p.server RPC interface to display @@ -203,22 +188,16 @@ func (self *Hive) NodeInfo() interface{} { func (self *Hive) PeerInfo(id discover.NodeID) interface{} { self.lock.Lock() defer self.lock.Unlock() - addr := NewPeerAddrFromNodeId(adapters.NewNodeId(id[:])) + addr := NewAddrFromNodeId(adapters.NewNodeId(id[:])) return interface{}(addr) } -// Stop terminates the updateloop -func (self *Hive) Stop() { - // closing toggle channel quits the updateloop - close(self.quit) -} - -func (self *Hive) Healthy() bool { - // TODO: determine if we have enough peers to consider the network - // to be healthy - return true +func (self *Hive) Register(peers chan OverlayAddr) error { + defer self.wake() + return self.Overlay.Register(peers) } +// wake triggers func (self *Hive) wake() { select { case self.more <- true: @@ -229,7 +208,100 @@ func (self *Hive) wake() { } } +// HexToBytes reads a hex string ontp func HexToBytes(s string) []byte { id := discover.MustHexID(s) return id[:] } + +// ToAddr returns the serialisable version of u +func ToAddr(pa OverlayPeer) *bzzAddr { + if addr, ok := pa.(*bzzAddr); ok { + return addr + } + if p, ok := pa.(*discPeer); ok { + return p.bzzAddr + } + return pa.(*bzzPeer).bzzAddr +} + +type hiveTicker interface { + Ch() <-chan time.Time + Stop() +} + +type timeTicker struct { + *time.Ticker +} + +func (t *timeTicker) Ch() <-chan time.Time { + return t.C +} + +// keepAlive is a forever loop +// in its awake state it periodically triggers connection attempts +// by writing to self.more until Kademlia Table is saturated +// wake state is toggled by writing to self.toggle +// it goes to sleep mode if table is saturated +// it restarts if the table becomes non-full again due to disconnections +func (self *Hive) keepAlive() { + if self.tick == nil { + ticker := time.NewTicker(self.KeepAliveInterval) + defer ticker.Stop() + self.tick = ticker.C + } + for { + select { + case <-self.tick: + log.Debug("wake up: make hive alive") + self.wake() + case <-self.quit: + return + } + } +} + +// loadPeers, savePeer implement persistence callback/ +func (self *Hive) loadPeers() error { + data, err := self.store.Load("peers") + if err != nil { + return err + } + if data == nil { + return nil + } + var as []*bzzAddr + if err := json.Unmarshal(data, &as); err != nil { + return err + } + + c := make(chan OverlayAddr) + go func() { + defer close(c) + for _, a := range as { + c <- a + } + }() + return self.Overlay.Register(c) +} + +// savePeers, savePeer implement persistence callback/ +func (self *Hive) savePeers() error { + var peers []*bzzAddr + self.Overlay.EachAddr(nil, 256, func(pa OverlayAddr, i int) bool { + if pa == nil { + log.Warn(fmt.Sprintf("empty addr: %v", i)) + return true + } + peers = append(peers, ToAddr(pa)) + return true + }) + data, err := json.Marshal(peers) + if err != nil { + return fmt.Errorf("could not encode peers: %v", err) + } + if err := self.store.Save("peers", data); err != nil { + return fmt.Errorf("could not save peers: %v", err) + } + return nil +} diff --git a/swarm/network/hive_test.go b/swarm/network/hive_test.go index 54a8c9e3bf..5a1b3f6ca8 100644 --- a/swarm/network/hive_test.go +++ b/swarm/network/hive_test.go @@ -1,67 +1,19 @@ package network import ( - "sync" "testing" "time" - "github.com/ethereum/go-ethereum/p2p/simulations/adapters" p2ptest "github.com/ethereum/go-ethereum/p2p/testing" ) -type testConnect struct { - mu sync.Mutex - conns []string - connectf func(c string) error - ticker chan time.Time -} - -func (self *testConnect) ping() <-chan time.Time { - return self.ticker -} - -func (self *testConnect) connect(na string) error { - self.mu.Lock() - defer self.mu.Unlock() - self.conns = append(self.conns, na) - self.connectf(na) - return nil -} - func newHiveTester(t *testing.T, params *HiveParams) (*bzzTester, *Hive) { // setup - addr := RandomAddr() // tested peers peer address - to := NewTestOverlay(addr.OverlayAddr()) // overlay topology drive - pp := NewHive(params, to) // hive - - ct := BzzCodeMap(DiscoveryMsgs...) // bzz protocol code map - services := func(p Peer) error { - pp.Add(p) - p.DisconnectHook(func(err error) { - pp.Remove(p) - }) - return nil - } - - return newBzzBaseTester(t, 1, addr, ct, services), pp -} - -func TestOverlayRegistration(t *testing.T) { - params := NewHiveParams() - params.Discovery = false - s, pp := newHiveTester(t, params) - defer s.Stop() - - id := s.Ids[0] - raddr := NewPeerAddrFromNodeId(id) - - s.runHandshakes() - - // hive should have called the overlay - if pp.Overlay.(*testOverlay).posMap[string(raddr.OverlayAddr())] == nil { - t.Fatalf("Overlay#On not called on new peer") - } + addr := RandomAddr() // tested peers peer address + to := NewKademlia(addr.OAddr, NewKadParams()) + pp := NewHive(params, to, nil) // hive + return newBzzBaseTester(t, 1, addr, DiscoverySpec, pp.Run), pp } func TestRegisterAndConnect(t *testing.T) { @@ -70,30 +22,23 @@ func TestRegisterAndConnect(t *testing.T) { defer s.Stop() id := s.Ids[0] - raddr := NewPeerAddrFromNodeId(id) + raddr := NewAddrFromNodeId(id) - pp.Register(raddr) + ch := make(chan OverlayAddr) + go func() { + ch <- raddr + close(ch) + }() + pp.Register(ch) // start the hive and wait for the connection - tc := &testConnect{ - connectf: func(c string) error { - s.Connect(adapters.NewNodeIdFromHex(c)) - return nil - }, - ticker: make(chan time.Time), - } - pp.Start(s, tc.ping) + tick := make(chan time.Time) + pp.tick = tick + pp.Start(s.Server) defer pp.Stop() - tc.ticker <- time.Now() - - s.runHandshakes() - - if pp.Overlay.(*testOverlay).posMap[string(raddr.OverlayAddr())] == nil { - t.Fatalf("Overlay#On not called on new peer") - } - + tick <- time.Now() // retrieve and broadcast - ord := order(raddr.OverlayAddr()) + ord := raddr.Over()[0] / 32 o := 0 if ord == 0 { o = 1 diff --git a/swarm/network/kademlia.go b/swarm/network/kademlia.go index 789575e804..6be4feebf9 100644 --- a/swarm/network/kademlia.go +++ b/swarm/network/kademlia.go @@ -23,6 +23,8 @@ import ( "time" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/discover" + "github.com/ethereum/go-ethereum/p2p/simulations/adapters" "github.com/ethereum/go-ethereum/pot" ) @@ -46,24 +48,20 @@ a guaranteed constant maximum limit on the number of hops needed to reach one node from the other. */ -type KadDiscovery interface { - NotifyPeer(Peer, uint8) error - NotifyProx(uint8) error -} - +// KadParams holds the config params for Kademlia type KadParams struct { // adjustable parameters - MaxProxDisplay int - MinProxBinSize int - MinBinSize int - MaxBinSize int - RetryInterval int - RetryExponent int - MaxRetries int - PruneInterval int + MaxProxDisplay int // number of rows the table shows + MinProxBinSize int // nearest neighbour core minimum cardinality + MinBinSize int // minimum number of peers in a row + MaxBinSize int // maximum number of peers in a row before pruning + RetryInterval int // initial interval before a peer is first redialed + RetryExponent int // exponent to multiply retry intervals with + MaxRetries int // maximum number of redial attempts + PruneInterval int // interval between peer pruning cycles } -// NewKadParams() returns a params struct with default values +// NewKadParams returns a params struct with default values func NewKadParams() *KadParams { return &KadParams{ MaxProxDisplay: 8, @@ -79,141 +77,103 @@ func NewKadParams() *KadParams { // Kademlia is a table of live peers and a db of known peers type Kademlia struct { - addr PeerAddr // immutable baseaddress of the table - // addr *pot.HashAddress // immutable baseaddress of the table - *KadParams // Kademlia configuration parameters - addrs, peers *pot.Pot // pots container for peers - lastProxLimit uint8 // stores the last calculated proxlimit + *KadParams // Kademlia configuration parameters + base []byte // immutable baseaddress of the table + addrs *pot.Pot // pots container for known peer addresses + conns *pot.Pot // pots container for live peer connections + depth uint8 // stores the last calculated depth } -// NewKademlia(addr, params) creates a Kademlia table for base address addr +// NewKademlia creates a Kademlia table for base address addr // with parameters as in params // if params is nil, it uses default values func NewKademlia(addr []byte, params *KadParams) *Kademlia { if params == nil { params = NewKadParams() } - self := &Kademlia{ - addr: &peerAddr{OAddr: addr}, + return &Kademlia{ + base: addr, KadParams: params, addrs: pot.NewPot(nil, 0), - peers: pot.NewPot(nil, 0), + conns: pot.NewPot(nil, 0), } - return self } -// Prune implements a forever loop reacting to a ticker time channel given -// as the first argument -// the loop quits if the channel is closed -// it checks each kademlia bin and if the peer count is higher than -// the MaxBinSize parameter it drops the oldest n peers such that -// the bin is reduced to MinBinSize peers thus leaving slots to newly -// connecting peers -func (self *Kademlia) Prune(c <-chan time.Time) { - go func() { - for _ = range c { - log.Debug("pruning...") - total := 0 - self.peers.EachBin(self.addr, 0, func(po, size int, f func(func(pot.PotVal, int) bool) bool) bool { - extra := size - self.MinBinSize - if size > self.MaxBinSize { - n := 0 - f(func(v pot.PotVal, po int) bool { - p := v.(*KadPeer).Peer - if p != nil { - p.Drop(fmt.Errorf("bucket full")) - } - n++ - return n < extra - }) - total += extra - } - return true - }) - log.Debug(fmt.Sprintf("pruned %v peers", total)) - } - }() +type Notifier interface { + NotifyPeer(OverlayAddr, uint8) error + NotifyDepth(uint8) error } -// KadPeer represents a Kademlia Peer and extends -// * PeerAddr interface (overlay and underlay addresses) -// * Peer interface (id, last seen, drop) -type KadPeer struct { - PeerAddr - Peer Peer +// OverlayPeer interface captures the common aspect of view of a peer from the Overlay +// topology driver +type OverlayPeer interface { + Address() []byte +} + +// OverlayConn represents a connected peer +type OverlayConn interface { + OverlayPeer + Drop(error) // call to indicate a peer should be expunged + Off() OverlayAddr // call to return a persitent OverlayAddr +} + +type OverlayAddr interface { + OverlayPeer + Update(OverlayAddr) OverlayAddr // returns the updated version of the original +} + +// entry represents a Kademlia table entry (an extension of OverlayPeer) +// implements the pot.PotVal interface via BytesAddress, so entry can be +// used directly as a pot element +type entry struct { + pot.PotVal + OverlayPeer seenAt time.Time retries int } -func (self *KadPeer) String() string { - if self == nil { - return "" - } - return fmt.Sprintf("%x", self.OverlayAddr()) -} - -func (self *Kademlia) callable(val pot.PotVal) *KadPeer { - kp := val.(*KadPeer) - // not callable if peer is live or exceeded maxRetries - if kp.Peer != nil || kp.retries > self.MaxRetries { - log.Trace(fmt.Sprintf("peer %v (%T) not callable", kp, kp.Peer)) - return nil - } - // calculate the allowed number of retries based on time lapsed since last seen - timeAgo := time.Since(kp.seenAt) - var retries int - for delta := int(timeAgo) / self.RetryInterval; delta > 0; delta /= self.RetryExponent { - log.Trace(fmt.Sprintf("delta: %v", delta)) - retries++ - } - - // this is never called concurrently, so safe to increment - // peer can be retried again - if retries < kp.retries { - log.Trace(fmt.Sprintf("log time needed before retry %v, wait only warrants %v", kp.retries, retries)) - return nil - } - kp.retries++ - log.Trace(fmt.Sprintf("peer %v is callable", kp)) - - return kp -} - -// NewKadPeer creates a kademlia peer from a PeerAddr interface -func NewKadPeer(na PeerAddr) *KadPeer { - return &KadPeer{ - PeerAddr: na, - seenAt: time.Now(), +// newEntry creates a kademlia peer from an OverlayPeer interface +func newEntry(p OverlayPeer) *entry { + return &entry{ + PotVal: pot.NewBytesVal(p, nil), + OverlayPeer: p, + seenAt: time.Now(), } } -// retrieve the base address -// which is the overlayaddress used by peers to reach us -func (self *Kademlia) GetAddr() PeerAddr { - return self.addr +func (self *entry) addr() OverlayAddr { + a, _ := self.OverlayPeer.(OverlayAddr) + return a } -// Register(nas) enters each PeerAddr as kademlia peers into the -// database of known peers -func (self *Kademlia) Register(nas ...PeerAddr) error { - label := fmt.Sprintf("%x", RandomAddr().OverlayAddr()) +func (self *entry) conn() OverlayConn { + c, _ := self.OverlayPeer.(OverlayConn) + return c +} + +func (self *entry) String() string { + return fmt.Sprintf("%x", self.OverlayPeer.Address()) +} + +// Register enters each OverlayAddr as kademlia peer record into the +// database of known peer addresses +func (self *Kademlia) Register(peers chan OverlayAddr) error { np := pot.NewPot(nil, 0) - for _, na := range nas { - if bytes.Equal(na.OverlayAddr(), self.addr.OverlayAddr()) { - log.Warn(fmt.Sprintf("[%06s] add peers: %x is self.. skipped ", label, self.addr.OverlayAddr())) - continue + for p := range peers { + // error if self received, peer should know better + if bytes.Equal(p.Address(), self.base) { + return fmt.Errorf("add peers: %x is self", self.base) } - p := NewKadPeer(na) - np, _, _ = pot.Add(np, pot.PotVal(p)) + np, _, _ = pot.Add(np, newEntry(p)) } - oldpeers := pot.NewPot(nil, 0) - oldpeers.Merge(self.addrs) - self.addrs.Merge(np) + com := self.addrs.Merge(np) + log.Debug(fmt.Sprintf("merged %v peers, %v known, total: %v", np.Size(), com, self.addrs.Size())) + // log.Trace(fmt.Sprintf("merged %v peers, %v known", np.Size(), com)) + + // TODO: remove this check m := make(map[string]bool) self.addrs.Each(func(val pot.PotVal, i int) bool { _, found := m[val.String()] - // TODO: remove this check - // log.Debug(fmt.Sprintf("-> %v %v", val, i)) if found { panic("duplicate found") } @@ -223,148 +183,34 @@ func (self *Kademlia) Register(nas ...PeerAddr) error { return nil } -// On(p) inserts the peer as a kademlia peer into the live peers -func (self *Kademlia) On(p Peer) { - kp := NewKadPeer(p) - kp.Peer = p - self.peers.Swap(kp, func(v pot.PotVal) pot.PotVal { - // if not found live - if v == nil { - // switch the offline peer - self.addrs.Swap(kp, func(v pot.PotVal) pot.PotVal { - return pot.PotVal(kp) - }) - // insert new peer - return pot.PotVal(kp) - } - // found among live peers, do nothing - return v - }) - prox := self.proxLimit() - - vp, ok := kp.Peer.(KadDiscovery) - if !ok { - // log.Trace(fmt.Sprintf("not discovery peer %T", kp)) - return - } - go vp.NotifyProx(uint8(prox)) - f := func(val pot.PotVal, po int) { - dp := val.(*KadPeer).Peer.(KadDiscovery) - log.Debug(fmt.Sprintf("peer %v notified of %v (%v)", dp, kp, po)) - dp.NotifyPeer(kp.Peer, uint8(po)) - if uint8(prox) != self.lastProxLimit { - self.lastProxLimit = uint8(prox) - dp.NotifyProx(uint8(prox)) - } - log.Debug("peer notified") - } - self.peers.EachNeighbourAsync(kp, 1024, 255, f, false) -} - -// Off removes a peer from among live peers -func (self *Kademlia) Off(p Peer) { - kp := NewKadPeer(p) - self.addrs.Swap(kp, func(v pot.PotVal) pot.PotVal { - if v != nil { - self.peers.Swap(kp, func(v pot.PotVal) pot.PotVal { - return nil - }) - } - return nil - }) -} - -type ByteAddr struct { - key []byte -} - -// EachLivePeer(base, po, f) is an iterator applying f to each live peer -// that has proximity order po or less as measured from the base -// if base is nil, kademlia base address is used -func (self *Kademlia) EachLivePeer(base []byte, o int, f func(Peer, int, bool) bool) { - var p pot.PotVal - if base == nil { - p = pot.PotVal(self.addr) - } else { - p = pot.PotVal(&peerAddr{OAddr: base}) - } - self.peers.EachNeighbour(p, func(val pot.PotVal, po int) bool { - if po > o { - return true - } - isproxbin := false - if l, _ := p.PO(val, 0); l >= self.proxLimit() { - isproxbin = true - } - return f(val.(*KadPeer).Peer, po, isproxbin) - }) -} - -// EachPeer(base, po, f) is an iterator applying f to each known peer -// that has proximity order po or less as measured from the base -// if base is nil, kademlia base address is used -func (self *Kademlia) EachPeer(base []byte, o int, f func(PeerAddr, int) bool) { - var p pot.PotVal - if base == nil { - p = pot.PotVal(self.addr) - } else { - p = pot.NewHashAddressFromBytes(base) - } - self.addrs.EachNeighbour(p, func(val pot.PotVal, po int) bool { - if po > o { - return true - } - return f(val.(*KadPeer).Peer, po) - }) -} - -// proxLimit() returns the proximity order that defines the distance of -// the nearest neighbour set with cardinality >= MinProxBinSize -// if there is altogether less than MinProxBinSize peers it returns 0 -func (self *Kademlia) proxLimit() int { - if self.peers.Size() < self.MinProxBinSize { - return 0 - } - var proxLimit int - var size int - f := func(v pot.PotVal, i int) bool { - size++ - proxLimit = i - return size < self.MinProxBinSize - } - self.peers.EachNeighbour(pot.PotVal(self.addr), f) - return proxLimit -} - // SuggestPeer returns a known peer for the lowest proximity bin for the -// lowest bincount below proxLimit +// lowest bincount below depth // naturally if there is an empty row it returns a peer for that // -func (self *Kademlia) SuggestPeer() (p PeerAddr, o int, want bool) { +func (self *Kademlia) SuggestPeer() (a OverlayAddr, o int, want bool) { minsize := self.MinBinSize - proxLimit := self.proxLimit() + depth := self.Depth() // if there is a callable neighbour within the current proxBin, connect // this makes sure nearest neighbour set is fully connected - log.Trace(fmt.Sprintf("candidate prox peer checking above PO %v", proxLimit)) + log.Debug(fmt.Sprintf("candidate prox peer checking above PO %v", depth)) + // log.Trace(fmt.Sprintf("candidate prox peer checking above PO %v", depth)) var ppo int - self.addrs.EachNeighbour(self.addr, func(val pot.PotVal, po int) bool { - r := self.callable(val) - if r == nil { - return po >= proxLimit - } - p = r + ba := pot.NewBytesVal(self.base, nil) + self.addrs.EachNeighbour(ba, func(val pot.PotVal, po int) bool { + a = self.callable(val) + log.Trace(fmt.Sprintf("candidate prox peer at %x: %v (%v). a == nil is %v", val.(*entry).Address(), a, po, a == nil)) ppo = po - return false + return a == nil && po >= depth }) - if p != nil { - log.Trace(fmt.Sprintf("candidate prox peer found: %v (%v), %v", p, ppo, p)) - return p, 0, false + if a != nil { + log.Trace(fmt.Sprintf("candidate prox peer found: %v (%v)", a, ppo)) + return a, 0, false } - log.Trace(fmt.Sprintf("no candidate prox peers to connect to (ProxLimit: %v, minProxSize: %v)", proxLimit, self.MinProxBinSize)) + log.Trace(fmt.Sprintf("no candidate prox peers to connect to (Depth: %v, minProxSize: %v) %#v", depth, self.MinProxBinSize, a)) var bpo []int prev := -1 - self.peers.EachBin(self.addr, 0, func(po, size int, f func(func(val pot.PotVal, i int) bool) bool) bool { + self.conns.EachBin(pot.NewBytesVal(self.base, nil), 0, func(po, size int, f func(func(val pot.PotVal, i int) bool) bool) bool { log.Trace(fmt.Sprintf("check PO%02d: ", po)) prev++ if po > prev { @@ -375,7 +221,7 @@ func (self *Kademlia) SuggestPeer() (p PeerAddr, o int, want bool) { minsize = size bpo = append(bpo, po) } - return size > 0 && po < proxLimit + return size > 0 && po < depth }) // all buckets are full // minsize == self.MinBinSize @@ -387,46 +233,186 @@ func (self *Kademlia) SuggestPeer() (p PeerAddr, o int, want bool) { // try to select a candidate peer for i := len(bpo) - 1; i >= 0; i-- { // find the first callable peer - self.addrs.EachBin(self.addr, bpo[i], func(po, size int, f func(func(val pot.PotVal, i int) bool) bool) bool { + self.addrs.EachBin(ba, bpo[i], func(po, size int, f func(func(pot.PotVal, int) bool) bool) bool { // for each bin we find callable candidate peers - f(func(val pot.PotVal, i int) bool { - r := self.callable(val) - log.Trace(fmt.Sprintf("check PO%02d: ", po)) - if r == nil { - return i < proxLimit - } - p = r - return false + log.Trace(fmt.Sprintf("check PO%02d: ", po)) + f(func(val pot.PotVal, j int) bool { + a = self.callable(val) + return a == nil && po < depth }) return false }) // found a candidate - if p != nil { + if a != nil { break } // cannot find a candidate, ask for more for this proximity bin specifically o = bpo[i] want = true } - return p, o, want + return a, o, want } -// kademlia table + kaddb table displayed with ascii +// On inserts the peer as a kademlia peer into the live peers +func (self *Kademlia) On(p OverlayConn) { + e := newEntry(p) + self.conns.Swap(p, func(v pot.PotVal) pot.PotVal { + // if not found live + if v == nil { + // insert new online peer into addrs + self.addrs.Swap(p, func(v pot.PotVal) pot.PotVal { + return e + }) + // insert new online peer into conns + return e + } + // found among live peers, do nothing + return v + }) + + np, ok := p.(Notifier) + if !ok { + return + } + + depth := uint8(self.Depth()) + if depth != self.depth { + self.depth = depth + } else { + depth = 0 + } + + go np.NotifyDepth(depth) + f := func(val pot.PotVal, po int) { + dp := val.(*entry).OverlayPeer.(Notifier) + dp.NotifyPeer(p.Off(), uint8(po)) + // log.Trace(fmt.Sprintf("peer %v notified of %v (%v)", dp, p, po)) + log.Debug(fmt.Sprintf("peer %v notified of %v (%v)", dp, p, po)) + if depth > 0 { + dp.NotifyDepth(depth) + log.Debug(fmt.Sprintf("peer %v notified of new depth %v", dp, depth)) + // log.Trace(fmt.Sprintf("peer %v notified of new depth %v", dp, depth)) + } + } + self.conns.EachNeighbourAsync(e, 1024, 255, f, false) +} + +// Off removes a peer from among live peers +func (self *Kademlia) Off(p OverlayConn) { + self.addrs.Swap(p, func(v pot.PotVal) pot.PotVal { + // v cannot be nil, must check otherwise we overwrite entry + if v == nil { + panic(fmt.Sprintf("connected peer not found %v", p)) + } + self.conns.Swap(p, func(v pot.PotVal) pot.PotVal { + // v cannot be nil, but no need to check + return nil + }) + return newEntry(p.Off()) + }) +} + +// EachConn is an iterator with args (base, po, f) applies f to each live peer +// that has proximity order po or less as measured from the base +// if base is nil, kademlia base address is used +func (self *Kademlia) EachConn(base []byte, o int, f func(OverlayConn, int, bool) bool) { + if len(base) == 0 { + base = self.base + } + p := pot.NewBytesVal(base, nil) + self.conns.EachNeighbour(p, func(val pot.PotVal, po int) bool { + if po > o { + return true + } + isproxbin := false + if l, _ := p.PO(val, 0); l >= self.Depth() { + isproxbin = true + } + return f(val.(*entry).conn(), po, isproxbin) + }) +} + +// EachAddr(base, po, f) is an iterator applying f to each known peer +// that has proximity order po or less as measured from the base +// if base is nil, kademlia base address is used +func (self *Kademlia) EachAddr(base []byte, o int, f func(OverlayAddr, int) bool) { + if len(base) == 0 { + base = self.base + } + p := pot.NewBytesVal(base, nil) + self.addrs.EachNeighbour(p, func(val pot.PotVal, po int) bool { + if po > o { + return true + } + return f(val.(*entry).addr(), po) + }) +} + +// Depth returns the proximity order that defines the distance of +// the nearest neighbour set with cardinality >= MinProxBinSize +// if there is altogether less than MinProxBinSize peers it returns 0 +func (self *Kademlia) Depth() (depth int) { + if self.conns.Size() < self.MinProxBinSize { + return 0 + } + var size int + f := func(v pot.PotVal, i int) bool { + size++ + depth = i + return size < self.MinProxBinSize + } + self.conns.EachNeighbour(pot.NewBytesVal(self.base, nil), f) + return depth +} + +func (self *Kademlia) callable(val pot.PotVal) OverlayAddr { + e := val.(*entry) + // not callable if peer is live or exceeded maxRetries + if e.conn() != nil || e.retries > self.MaxRetries { + log.Trace(fmt.Sprintf("peer %v (%T) not callable", e, e.OverlayPeer)) + return nil + } + // calculate the allowed number of retries based on time lapsed since last seen + timeAgo := time.Since(e.seenAt) + var retries int + for delta := int(timeAgo) / self.RetryInterval; delta > 0; delta /= self.RetryExponent { + log.Trace(fmt.Sprintf("delta: %v", delta)) + retries++ + } + + // this is never called concurrently, so safe to increment + // peer can be retried again + if retries < e.retries { + log.Trace(fmt.Sprintf("long time since last try (at %v) needed before retry %v, wait only warrants %v", timeAgo, e.retries, retries)) + return nil + } + e.retries++ + log.Trace(fmt.Sprintf("peer %v is callable", e)) + + return e.addr() +} + +// BaseAddr return the kademlia base addres +func (self *Kademlia) BaseAddr() []byte { + return self.base +} + +// String returns kademlia table + kaddb table displayed with ascii func (self *Kademlia) String() string { var rows []string rows = append(rows, "=========================================================================") - rows = append(rows, fmt.Sprintf("%v KΛÐΞMLIΛ hive: queen's address: %v", time.Now().UTC().Format(time.UnixDate), fmt.Sprintf("%x", self.addr.OverlayAddr()[:3]))) - rows = append(rows, fmt.Sprintf("population: %d (%d), MinProxBinSize: %d, MinBinSize: %d, MaxBinSize: %d", self.peers.Size(), self.addrs.Size(), self.MinProxBinSize, self.MinBinSize, self.MaxBinSize)) + rows = append(rows, fmt.Sprintf("%v KΛÐΞMLIΛ hive: queen's address: %x", time.Now().UTC().Format(time.UnixDate), self.BaseAddr()[:3])) + rows = append(rows, fmt.Sprintf("population: %d (%d), MinProxBinSize: %d, MinBinSize: %d, MaxBinSize: %d", self.conns.Size(), self.addrs.Size(), self.MinProxBinSize, self.MinBinSize, self.MaxBinSize)) liverows := make([]string, self.MaxProxDisplay) peersrows := make([]string, self.MaxProxDisplay) - var proxLimit int + var depth int prev := -1 - var proxLimitSet bool - rest := self.peers.Size() - self.peers.EachBin(self.addr, 0, func(po, size int, f func(func(val pot.PotVal, i int) bool) bool) bool { + var depthSet bool + rest := self.conns.Size() + self.conns.EachBin(pot.NewBytesVal(self.base, nil), 0, func(po, size int, f func(func(val pot.PotVal, i int) bool) bool) bool { var rowlen int if po >= self.MaxProxDisplay { po = self.MaxProxDisplay - 1 @@ -434,13 +420,13 @@ func (self *Kademlia) String() string { row := []string{fmt.Sprintf("%2d", size)} rest -= size f(func(val pot.PotVal, vpo int) bool { - row = append(row, val.(*KadPeer).String()[:6]) + row = append(row, val.(*entry).String()[:6]) rowlen++ return rowlen < 4 }) - if !proxLimitSet && (po > prev+1 || rest < self.MinProxBinSize) { - proxLimitSet = true - proxLimit = prev + 1 + if !depthSet && (po > prev+1 || rest < self.MinProxBinSize) { + depthSet = true + depth = prev + 1 } for ; rowlen <= 5; rowlen++ { row = append(row, " ") @@ -450,7 +436,7 @@ func (self *Kademlia) String() string { return true }) - self.addrs.EachBin(self.addr, 0, func(po, size int, f func(func(val pot.PotVal, i int) bool) bool) bool { + self.addrs.EachBin(pot.NewBytesVal(self.base, nil), 0, func(po, size int, f func(func(val pot.PotVal, i int) bool) bool) bool { var rowlen int if po >= self.MaxProxDisplay { po = self.MaxProxDisplay - 1 @@ -461,8 +447,7 @@ func (self *Kademlia) String() string { row := []string{fmt.Sprintf("%2d", size)} // we are displaying live peers too f(func(val pot.PotVal, vpo int) bool { - kp := val.(*KadPeer) - row = append(row, kp.String()[:6]) + row = append(row, val.(*entry).String()[:6]) rowlen++ return rowlen < 4 }) @@ -471,7 +456,7 @@ func (self *Kademlia) String() string { }) for i := 0; i < self.MaxProxDisplay; i++ { - if i == proxLimit { + if i == depth { rows = append(rows, fmt.Sprintf("============ PROX LIMIT: %d ==========================================", i)) } left := liverows[i] @@ -487,3 +472,95 @@ func (self *Kademlia) String() string { rows = append(rows, "=========================================================================") return "\n" + strings.Join(rows, "\n") } + +// Prune implements a forever loop reacting to a ticker time channel given +// as the first argument +// the loop quits if the channel is closed +// it checks each kademlia bin and if the peer count is higher than +// the MaxBinSize parameter it drops the oldest n peers such that +// the bin is reduced to MinBinSize peers thus leaving slots to newly +// connecting peers +func (self *Kademlia) Prune(c <-chan time.Time) { + go func() { + for range c { + total := 0 + self.conns.EachBin(pot.NewBytesVal(self.base, nil), 0, func(po, size int, f func(func(pot.PotVal, int) bool) bool) bool { + extra := size - self.MinBinSize + if size > self.MaxBinSize { + n := 0 + f(func(v pot.PotVal, po int) bool { + v.(*entry).conn().Drop(fmt.Errorf("bucket full")) + n++ + return n < extra + }) + total += extra + } + return true + }) + log.Debug(fmt.Sprintf("pruned %v peers", total)) + } + }() +} + +func NewPeerPot(kadMinProxSize int, ids ...*adapters.NodeId) map[discover.NodeID][][]byte { + // create a table of all nodes for health check + np := pot.NewPot(nil, 0) + for _, id := range ids { + o := ToOverlayAddr(id.Bytes()) + np, _, _ = pot.Add(np, pot.NewBytesVal(o, nil)) + } + nnmap := make(map[discover.NodeID][][]byte) + + for _, id := range ids { + pl := 0 + var nns [][]byte + np.EachNeighbour(pot.NewBytesVal(id.Bytes(), nil), func(val pot.PotVal, po int) bool { + a := val.(pot.BytesAddress).Address() + nns = append(nns, a) + if len(nns) >= kadMinProxSize { + pl = po + } + return pl == 0 || pl == po + }) + nnmap[id.NodeID] = nns + } + return nnmap +} + +func (self *Kademlia) FirstEmptyBin() (i int) { + i = -1 + self.conns.EachBin(pot.NewBytesVal(self.base, nil), 0, func(po, size int, f func(func(val pot.PotVal, i int) bool) bool) bool { + if po > i+1 { + i = po + return false + } + i = po + return true + }) + return i +} + +func (self *Kademlia) Full() bool { + return self.FirstEmptyBin() >= self.Depth() +} + +// Healthy reports the health state of the kademlia connectivity +// +func (self *Kademlia) Healthy(peers [][]byte) bool { + return self.gotNearestNeighbours(peers) && self.Full() +} + +func (self *Kademlia) gotNearestNeighbours(peers [][]byte) (got bool) { + pm := make(map[string]bool) + for _, p := range peers { + pm[string(p)] = true + } + self.EachConn(nil, 255, func(p OverlayConn, po int, nn bool) bool { + if !nn { + return false + } + _, got = pm[string(p.Address())] + return got + }) + return got +} diff --git a/swarm/network/kademlia_test.go b/swarm/network/kademlia_test.go index 81d5184bbf..1d1ebfba17 100644 --- a/swarm/network/kademlia_test.go +++ b/swarm/network/kademlia_test.go @@ -17,6 +17,7 @@ package network import ( "fmt" + "os" "sync" "testing" "time" @@ -25,9 +26,15 @@ import ( "github.com/ethereum/go-ethereum/pot" ) -func testKadPeerAddr(s string) *peerAddr { +func init() { + h := log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))) + // h := log.CallerFileHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(true))) + log.Root().SetHandler(h) +} + +func testKadPeerAddr(s string) *bzzAddr { a := pot.NewHashAddress(s).Bytes() - return &peerAddr{OAddr: a, UAddr: a} + return &bzzAddr{OAddr: a, UAddr: a} } type testDropPeer struct { @@ -58,23 +65,24 @@ type dropError struct { } func (self *testDropPeer) Drop(err error) { - err2 := &dropError{err, overlayStr(self)} + err2 := &dropError{err, binStr(self)} self.dropc <- err2 } -func (self *testDiscPeer) NotifyProx(po uint8) error { - key := overlayStr(self) +func (self *testDiscPeer) NotifyDepth(po uint8) error { + key := binStr(self) self.lock.Lock() defer self.lock.Unlock() self.notifications[key] = po return nil } -func (self *testDiscPeer) NotifyPeer(p Peer, po uint8) error { - key := overlayStr(self) - key += overlayStr(p) +func (self *testDiscPeer) NotifyPeer(p OverlayAddr, po uint8) error { + key := binStr(self) + key += binStr(p) self.lock.Lock() defer self.lock.Unlock() + log.Trace(fmt.Sprintf("key %v=>%v", key, po)) self.notifications[key] = po return nil } @@ -102,40 +110,13 @@ func newTestKademlia(b string) *testKademlia { } func (k *testKademlia) newTestKadPeer(s string) Peer { - dp := &testDropPeer{&bzzPeer{peerAddr: testKadPeerAddr(s)}, k.dropc} + dp := &testDropPeer{&bzzPeer{bzzAddr: testKadPeerAddr(s)}, k.dropc} if k.Discovery { return Peer(&testDiscPeer{dp, k.lock, k.notifications}) } return Peer(dp) } -func overlayStr(a PeerAddr) string { - log.Error(fmt.Sprintf("PeerAddr: %v (%T)", a, a)) - // if a == (*KadPeer)(nil) || a == (*testDiscPeer)(nil) || a == (*bzzPeer)(nil) || a == nil { - // return "" - // } - // var p Peer - // s, ok := a.(*KadPeer) - // if ok { - // p = s.Peer - // } else { - // p = a.(*testDiscPeer).Peer - // } - // log.Error(fmt.Sprintf("PeerAddr: %v (%T)", p, p)) - // if p == (Peer)(nil) || p == (*testDiscPeer)(nil) || p == (*bzzPeer)(nil) { - // return "" - // } - // return pot.NewHashAddressFromBytes(p.OverlayAddr()).Bin()[:6] - if a == nil { - return "" - } - k, ok := a.(*KadPeer) - if ok && k.Peer != nil { - return pot.ToBin(a.(*KadPeer).Peer.OverlayAddr())[:6] - } - return pot.ToBin(a.OverlayAddr())[:6] -} - func (k *testKademlia) On(ons ...string) *testKademlia { for _, s := range ons { p := k.newTestKadPeer(s) @@ -146,25 +127,30 @@ func (k *testKademlia) On(ons ...string) *testKademlia { func (k *testKademlia) Off(offs ...string) *testKademlia { for _, s := range offs { - k.Kademlia.Off(k.newTestKadPeer(s)) + k.Kademlia.Off(k.newTestKadPeer(s).(OverlayConn)) } return k } func (k *testKademlia) Register(regs ...string) *testKademlia { - var ps []PeerAddr - for _, s := range regs { - ps = append(ps, PeerAddr(testKadPeerAddr(s))) - } - k.Kademlia.Register(ps...) + ch := make(chan OverlayAddr) + go func() { + defer close(ch) + for _, s := range regs { + ch <- testKadPeerAddr(s) + } + }() + err := k.Kademlia.Register(ch) + log.Trace(fmt.Sprintf("register %v addresses: %v", len(regs), err)) + return k } func testSuggestPeer(t *testing.T, k *testKademlia, expAddr string, expPo int, expWant bool) error { addr, o, want := k.SuggestPeer() - if overlayStr(addr) != expAddr { - return fmt.Errorf("incorrect peer address suggested. expected %v, got %v", expAddr, overlayStr(addr)) + if binStr(addr) != expAddr { + return fmt.Errorf("incorrect peer address suggested. expected %v, got %v", expAddr, binStr(addr)) } if o != expPo { return fmt.Errorf("incorrect prox order suggested. expected %v, got %v", expPo, o) @@ -175,6 +161,13 @@ func testSuggestPeer(t *testing.T, k *testKademlia, expAddr string, expPo int, e return nil } +func binStr(a OverlayPeer) string { + if a == nil { + return "" + } + return pot.ToBin(a.Address())[:6] +} + func TestSuggestPeerFindPeers(t *testing.T) { // 2 row gap, unsaturated proxbin, no callables -> want PO 0 k := newTestKademlia("000000").On("001000") @@ -222,7 +215,6 @@ func TestSuggestPeerFindPeers(t *testing.T) { // second time disconnected peer not callable // with reasonably set Interval - // err = testSuggestPeer(t, k, "010000", 2, true) err = testSuggestPeer(t, k, "", 1, true) if err != nil { t.Fatal(err.Error()) @@ -237,16 +229,16 @@ func TestSuggestPeerFindPeers(t *testing.T) { } k.On("010000") - k.Off("010000") - // PO1 disconnects // new closer peer appears, it is immediately wanted - // k.Off("010000") k.Register("000101") err = testSuggestPeer(t, k, "000101", 0, false) if err != nil { t.Fatal(err.Error()) } + // PO1 disconnects + k.On("000101") + k.Off("010000") // second time, gap filling err = testSuggestPeer(t, k, "010000", 0, false) if err != nil { @@ -265,6 +257,19 @@ func TestSuggestPeerFindPeers(t *testing.T) { t.Fatal(err.Error()) } + k.Register("010001") + err = testSuggestPeer(t, k, "", 0, true) + if err != nil { + t.Fatal(err.Error()) + } + + k.On("100001") + log.Trace("Kad:\n%v", k.String()) + err = testSuggestPeer(t, k, "010001", 0, false) + if err != nil { + t.Fatal(err.Error()) + } + k.On("100001") k.On("010001") err = testSuggestPeer(t, k, "", 0, false) @@ -273,7 +278,18 @@ func TestSuggestPeerFindPeers(t *testing.T) { } k.MinBinSize = 3 + k.Register("100010") + err = testSuggestPeer(t, k, "100010", 0, false) + if err != nil { + t.Fatal(err.Error()) + } + k.On("100010") + err = testSuggestPeer(t, k, "", 1, true) + if err != nil { + t.Fatal(err.Error()) + } + k.On("010010") err = testSuggestPeer(t, k, "", 2, true) if err != nil { @@ -281,8 +297,16 @@ func TestSuggestPeerFindPeers(t *testing.T) { } k.On("001010") + err = testSuggestPeer(t, k, "", 3, true) + if err != nil { + log.Trace("Kad:\n%v", k.String()) + t.Fatal(err.Error()) + } + + k.On("000110") err = testSuggestPeer(t, k, "", 0, false) if err != nil { + log.Trace("Kad:\n%v", k.String()) t.Fatal(err.Error()) } @@ -440,7 +464,7 @@ func TestNotifications(t *testing.T) { k.Discovery = true k.MinProxBinSize = 3 k.On("010000", "001000") - time.Sleep(100 * time.Millisecond) + time.Sleep(1000 * time.Millisecond) err := k.checkNotifications( []*testPeerNotification{ &testPeerNotification{"010000", "001000", 1}, diff --git a/swarm/network/protocol.go b/swarm/network/protocol.go index 66963215e4..7753a69b1c 100644 --- a/swarm/network/protocol.go +++ b/swarm/network/protocol.go @@ -17,7 +17,11 @@ package network import ( + "context" + "errors" "fmt" + "net" + "sync" "time" "github.com/ethereum/go-ethereum/crypto" @@ -26,83 +30,213 @@ import ( "github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/protocols" "github.com/ethereum/go-ethereum/p2p/simulations/adapters" - "github.com/ethereum/go-ethereum/pot" + "github.com/ethereum/go-ethereum/rpc" ) const ( - ProtocolName = "bzz" - Version = 0 NetworkId = 322 // BZZ in l33t ProtocolMaxMsgSize = 10 * 1024 * 1024 ) -// bzz is the bzz protocol view of a protocols.Peer (itself an extension of p2p.Peer) -type bzzPeer struct { - *protocols.Peer - localAddr *peerAddr - *peerAddr // remote address - lastActive time.Time +var BzzHandshakeSpec = &protocols.Spec{ + Name: "bzz", + Version: 1, + MaxMsgSize: 10 * 1024 * 1024, + Messages: []interface{}{ + bzzHandshake{}, + }, } -func (self *bzzPeer) LastActive() time.Time { - return self.lastActive +var DiscoverySpec = &protocols.Spec{ + Name: "hive", + Version: 1, + MaxMsgSize: 10 * 1024 * 1024, + Messages: []interface{}{ + peersMsg{}, + getPeersMsg{}, + subPeersMsg{}, + }, } -// implemented by peerAddr -type PeerAddr interface { - OverlayAddr() []byte - UnderlayAddr() []byte - PO(pot.PotVal, int) (int, bool) +// the Addr interface that peerPool needs +type Addr interface { + OverlayPeer + Over() []byte + Under() []byte String() string + Update(OverlayAddr) OverlayAddr } -// the Peer interface that peerPool needs +// Peer interface represents an live peer connection type Peer interface { - PeerAddr - // String() string // pretty printable the Node - ID() discover.NodeID // the key that uniquely identifies the Node for the peerPool - Send(interface{}) error // can send messages - Drop(error) // disconnect this peer - Register(interface{}, func(interface{}) error) uint64 // register message-handler callbacks - DisconnectHook(func(error)) + Addr // the address of a peer + Conn // the live connection (protocols.Peer) + LastActive() time.Time // last time active } -func BzzCodeMap(msgs ...interface{}) *protocols.CodeMap { - ct := protocols.NewCodeMap(ProtocolName, Version, ProtocolMaxMsgSize) - ct.Register(&bzzHandshake{}) - ct.Register(msgs...) - return ct +// Conn interface represents an live peer connection +type Conn interface { + ID() discover.NodeID // the key that uniquely identifies the Node for the peerPool + Handshake(context.Context, interface{}) (interface{}, error) // can send messages + Send(interface{}) error // can send messages + Drop(error) // disconnect this peer + Run(func(interface{}) error) error // the run function to run a protocol + Off() OverlayAddr } -// Bzz is the protocol constructor -// returns p2p.Protocol that is to be offered by the node.Service -func Bzz(oAddr, uAddr []byte, ct *protocols.CodeMap, services func(Peer) error, peerInfo func(id discover.NodeID) interface{}, nodeInfo func() interface{}) *p2p.Protocol { - run := func(p *protocols.Peer) error { - bee := &bzzPeer{ - Peer: p, - localAddr: &peerAddr{oAddr, uAddr}, - } - // protocol handshake and its validation - // sets remote peer address - err := bee.bzzHandshake() - if err != nil { - log.Error(fmt.Sprintf("handshake error in peer %v: %v", bee.ID(), err)) +// TODO: implement store for exec nodes +type StateStore interface { + Load(string) ([]byte, error) + Save(string, []byte) error +} + +// BzzConfig captures the config params used by the hive +type BzzConfig struct { + OverlayAddr []byte + UnderlayAddr []byte + HiveParams *HiveParams +} + +// Bzz is the swarm protocol bundle +type Bzz struct { + Hive *Hive + localAddr *bzzAddr + mtx sync.Mutex + handshakes map[discover.NodeID]*bzzHandshake +} + +// NewBzz is the swarm protocol constructor +func NewBzz(config *BzzConfig, kad Overlay, store StateStore) *Bzz { + return &Bzz{ + Hive: NewHive(config.HiveParams, kad, store), + localAddr: &bzzAddr{config.OverlayAddr, config.UnderlayAddr}, + handshakes: make(map[discover.NodeID]*bzzHandshake), + } +} + +// Bzz implements the node.Service interface, offers Protocols +// * handshake/hive +// * discovery +func (b *Bzz) Protocols() []p2p.Protocol { + return []p2p.Protocol{ + { + Name: BzzHandshakeSpec.Name, + Version: BzzHandshakeSpec.Version, + Length: BzzHandshakeSpec.Length(), + Run: b.runHandshake, + }, + { + Name: DiscoverySpec.Name, + Version: DiscoverySpec.Version, + Length: DiscoverySpec.Length(), + Run: b.runProtocol(DiscoverySpec, b.Hive.Run), + NodeInfo: b.Hive.NodeInfo, + PeerInfo: b.Hive.PeerInfo, + }, + } +} + +// Bzz implements the node.Service interface, offers APIs: +// * hive +func (b *Bzz) APIs() []rpc.API { + return []rpc.API{{ + Namespace: "hive", + Version: "1.0", + Service: b.Hive, + }} +} + +func (b *Bzz) Start(server *p2p.Server) error { + return b.Hive.Start(server) +} + +func (b *Bzz) Stop() error { + b.Hive.Stop() + return nil +} + +func (b *Bzz) runHandshake(p *p2p.Peer, rw p2p.MsgReadWriter) error { + handshake := b.getHandshake(p.ID()) + defer b.removeHandshake(p.ID()) + + if err := handshake.Perform(p, rw); err != nil { + log.Error("handshake failed", "peer", p.ID(), "err", err) + return err + } + + // fail if we get another handshake + msg, err := rw.ReadMsg() + if err != nil { + return err + } + msg.Discard() + return errors.New("received multiple handshakes") +} + +func (b *Bzz) runProtocol(spec *protocols.Spec, run func(*bzzPeer) error) func(*p2p.Peer, p2p.MsgReadWriter) error { + return func(p *p2p.Peer, rw p2p.MsgReadWriter) error { + // wait for the bzz protocol to perform the handshake + handshake := b.getHandshake(p.ID()) + if err := handshake.Wait(); err != nil { return err } - // mount external service models on the peer connection (swap, sync, hive) - if services != nil { - err = services(bee) - if err != nil { - log.Error(fmt.Sprintf("protocol service error for peer %v: %v", bee.ID(), err)) - return err - } + // the handshake has succeeded so run the service + peer := &bzzPeer{ + Peer: protocols.NewPeer(p, rw, spec), + localAddr: b.localAddr, + bzzAddr: handshake.peerAddr, } - - return bee.Run() + return run(peer) } +} - return protocols.NewProtocol(ProtocolName, Version, run, ct, peerInfo, nodeInfo) +func (b *Bzz) removeHandshake(peerID discover.NodeID) { + b.mtx.Lock() + defer b.mtx.Unlock() + delete(b.handshakes, peerID) +} + +func (b *Bzz) getHandshake(peerID discover.NodeID) *bzzHandshake { + b.mtx.Lock() + defer b.mtx.Unlock() + handshake, ok := b.handshakes[peerID] + if !ok { + handshake = &bzzHandshake{ + Version: uint64(BzzHandshakeSpec.Version), + NetworkId: uint64(NetworkId), + Addr: b.localAddr, + done: make(chan struct{}), + } + b.handshakes[peerID] = handshake + } + return handshake +} + +// bzzPeer is the bzz protocol view of a protocols.Peer (itself an extension of p2p.Peer) +// implements the Peer interface and all interfaces Peer implements: Addr, OverlayPeer +type bzzPeer struct { + *protocols.Peer // represents the connection for online peers + localAddr *bzzAddr // local Peers address + *bzzAddr // remote address -> implements Addr interface = protocols.Peer + lastActive time.Time // time is updated whenever mutexes are releasing +} + +func newBzzPeer(p *protocols.Peer, over, under []byte) *bzzPeer { + return &bzzPeer{ + Peer: p, + localAddr: &bzzAddr{over, under}, + } +} + +// Off returns the overlay peer record for offline persistance +func (self *bzzPeer) Off() OverlayAddr { + return self.bzzAddr +} + +// LastActive returns the time the peer was last active +func (self *bzzPeer) LastActive() time.Time { + return self.lastActive } /* @@ -115,105 +249,84 @@ func Bzz(oAddr, uAddr []byte, ct *protocols.CodeMap, services func(Peer) error, type bzzHandshake struct { Version uint64 NetworkId uint64 - Addr *peerAddr + Addr *bzzAddr + + // peerAddr is the address received in the peer handshake + peerAddr *bzzAddr + + done chan struct{} + err error } func (self *bzzHandshake) String() string { return fmt.Sprintf("Handshake: Version: %v, NetworkId: %v, Addr: %v", self.Version, self.NetworkId, self.Addr) } -// peerAddr implements the PeerAddress interface -type peerAddr struct { +const bzzHandshakeTimeout = time.Second + +func (self *bzzHandshake) Perform(p *p2p.Peer, rw p2p.MsgReadWriter) (err error) { + defer func() { + self.err = err + close(self.done) + }() + peer := protocols.NewPeer(p, rw, BzzHandshakeSpec) + ctx, cancel := context.WithTimeout(context.Background(), bzzHandshakeTimeout) + defer cancel() + hs, err := peer.Handshake(ctx, self) + if err != nil { + return err + } + rhs := hs.(*bzzHandshake) + if rhs.NetworkId != self.NetworkId { + return fmt.Errorf("network id mismatch %d (!= %d)", rhs.NetworkId, self.NetworkId) + } + if rhs.Version != self.Version { + return fmt.Errorf("version mismatch %d (!= %d)", rhs.Version, self.Version) + } + self.peerAddr = rhs.Addr + return nil +} + +func (self *bzzHandshake) Wait() error { + select { + case <-self.done: + return self.err + case <-time.After(bzzHandshakeTimeout): + return errors.New("timed out waiting for bzz handshake") + } +} + +// bzzAddr implements the PeerAddr interface +type bzzAddr struct { OAddr []byte UAddr []byte } -func (self *peerAddr) OverlayAddr() []byte { +// implements OverlayPeer interface to be used in pot package +func (self *bzzAddr) Address() []byte { return self.OAddr } -func (self *peerAddr) UnderlayAddr() []byte { +// Over returns the overlay address +func (self *bzzAddr) Over() []byte { + return self.OAddr +} + +// Under retrun the underlay address +func (self *bzzAddr) Under() []byte { return self.UAddr } -func (self *peerAddr) PO(val pot.PotVal, pos int) (int, bool) { - kp := val.(PeerAddr) - one := kp.OverlayAddr() - other := self.OAddr - for i := pos / 8; i < len(one); i++ { - if one[i] == other[i] { - continue - } - oxo := one[i] ^ other[i] - start := 0 - if i == pos/8 { - start = pos % 8 - } - for j := start; j < 8; j++ { - if (uint8(oxo)>>uint8(7-j))&0x01 != 0 { - return i*8 + j, false - } - } - } - return len(one) * 8, true - // var ha *pot.HashAddress - // var left, right string - // if ok { - // ha = kp.HashAddress - // } else { - // ha = val.(*pot.HashAddress) - // } +func (self *bzzAddr) Update(a OverlayAddr) OverlayAddr { + return &bzzAddr{self.OAddr, a.(Addr).Under()} } -func (self *peerAddr) String() string { - return fmt.Sprintf("%x <%x>", self.OAddr, self.UAddr) -} - -// bzzHandshake negotiates the bzz master handshake -// and validates the response, returns error when -// mismatch/incompatibility is evident -func (self *bzzPeer) bzzHandshake() error { - - lhs := &bzzHandshake{ - Version: uint64(Version), - NetworkId: uint64(NetworkId), - Addr: self.localAddr, - } - - hs, err := self.Handshake(lhs) - if err != nil { - log.Error(fmt.Sprintf("handshake failed: %v", err)) - return err - } - - rhs := hs.(*bzzHandshake) - self.peerAddr = rhs.Addr - err = checkBzzHandshake(rhs) - if err != nil { - log.Error(fmt.Sprintf("handshake between %v and %v failed: %v", self.localAddr, self.peerAddr, err)) - return err - } - - return nil - -} - -// checkBzzHandshake checks for the validity and compatibility of the remote handshake -func checkBzzHandshake(rhs *bzzHandshake) error { - - if NetworkId != rhs.NetworkId { - return fmt.Errorf("network id mismatch %d (!= %d)", rhs.NetworkId, NetworkId) - } - - if Version != rhs.Version { - return fmt.Errorf("version mismatch %d (!= %d)", rhs.Version, Version) - } - - return nil +func (self *bzzAddr) String() string { + return fmt.Sprintf("%x <%s>", self.OAddr, self.UAddr) } // RandomAddr is a utility method generating an address from a public key -func RandomAddr() *peerAddr { +func RandomAddr() *bzzAddr { key, err := crypto.GenerateKey() if err != nil { panic("unable to generate key") @@ -221,23 +334,28 @@ func RandomAddr() *peerAddr { pubkey := crypto.FromECDSAPub(&key.PublicKey) var id discover.NodeID copy(id[:], pubkey[1:]) - return &peerAddr{ + return &bzzAddr{ OAddr: crypto.Keccak256(pubkey[1:]), UAddr: id[:], } } -// NodeId transforms the underlay address to an adapters.NodeId -func NodeId(addr PeerAddr) *adapters.NodeId { - return adapters.NewNodeId(addr.UnderlayAddr()) +// NewNodeIdFromAddr transforms the underlay address to an adapters.NodeId +func NewNodeIdFromAddr(addr Addr) *adapters.NodeId { + return adapters.NewNodeId(addr.Under()) } -// NewPeerAddrFromNodeId constucts a peerAddr from an adapters.NodeId +// NewAddrFromNodeId constucts a bzzAddr from an adapters.NodeId // the overlay address is derived as the hash of the nodeId -func NewPeerAddrFromNodeId(n *adapters.NodeId) *peerAddr { +func NewAddrFromNodeId(n *adapters.NodeId) Addr { id := n.NodeID - return &peerAddr{ - OAddr: crypto.Keccak256(id[:]), - UAddr: id[:], + return &bzzAddr{ + OAddr: ToOverlayAddr(n.Bytes()), + UAddr: []byte(discover.NewNode(id, net.IP{127, 0, 0, 1}, 30303, 30303).String()), } } + +// ToOverlayAddr creates an overlayaddress from NodeID +func ToOverlayAddr(id []byte) []byte { + return crypto.Keccak256(id) +} diff --git a/swarm/network/protocol_test.go b/swarm/network/protocol_test.go index cf750b614d..6848f49af0 100644 --- a/swarm/network/protocol_test.go +++ b/swarm/network/protocol_test.go @@ -2,14 +2,42 @@ package network import ( "fmt" + "sync" "testing" - "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/protocols" "github.com/ethereum/go-ethereum/p2p/simulations/adapters" p2ptest "github.com/ethereum/go-ethereum/p2p/testing" ) +type testStore struct { + sync.Mutex + + values map[string][]byte +} + +func newTestStore() *testStore { + return &testStore{values: make(map[string][]byte)} +} + +func (t *testStore) Load(key string) ([]byte, error) { + t.Lock() + defer t.Unlock() + v, ok := t.values[key] + if !ok { + return nil, fmt.Errorf("key not found: %s", key) + } + return v, nil +} + +func (t *testStore) Save(key string, v []byte) error { + t.Lock() + defer t.Unlock() + t.values[key] = v + return nil +} + func bzzHandshakeExchange(lhs, rhs *bzzHandshake, id *adapters.NodeId) []p2ptest.Exchange { return []p2ptest.Exchange{ @@ -34,21 +62,23 @@ func bzzHandshakeExchange(lhs, rhs *bzzHandshake, id *adapters.NodeId) []p2ptest } } -func newBzzBaseTester(t *testing.T, n int, addr *peerAddr, ct *protocols.CodeMap, services func(Peer) error) *bzzTester { - if ct == nil { - ct = BzzCodeMap() - } - +func newBzzBaseTester(t *testing.T, n int, addr *bzzAddr, spec *protocols.Spec, run func(*bzzPeer) error) *bzzTester { cs := make(map[string]chan bool) - srv := func(p Peer) error { + srv := func(p *bzzPeer) error { defer close(cs[p.ID().String()]) - return services(p) + return run(p) } - protocall := Bzz(addr.OverlayAddr(), addr.UnderlayAddr(), ct, srv, nil, nil).Run + protocall := func(p *p2p.Peer, rw p2p.MsgReadWriter) error { + return srv(&bzzPeer{ + Peer: protocols.NewPeer(p, rw, spec), + localAddr: addr, + bzzAddr: NewAddrFromNodeId(&adapters.NodeId{NodeID: p.ID()}), + }) + } - s := p2ptest.NewProtocolTester(t, NodeId(addr), n, protocall) + s := p2ptest.NewProtocolTester(t, NewNodeIdFromAddr(addr), n, protocall) for _, id := range s.Ids { cs[id.NodeID.String()] = make(chan bool) @@ -63,32 +93,27 @@ func newBzzBaseTester(t *testing.T, n int, addr *peerAddr, ct *protocols.CodeMap type bzzTester struct { *p2ptest.ProtocolTester - addr *peerAddr + addr *bzzAddr cs map[string]chan bool } -func newBzzTester(t *testing.T, n int, addr *peerAddr, pp *p2ptest.TestPeerPool, ct *protocols.CodeMap, services func(Peer) error) *bzzTester { +func newBzzTester(t *testing.T, n int, addr *bzzAddr, pp *p2ptest.TestPeerPool, spec *protocols.Spec, services func(Peer) error) *bzzTester { - extraservices := func(p Peer) error { + extraservices := func(p *bzzPeer) error { pp.Add(p) - p.DisconnectHook(func(err error) { - pp.Remove(p) - }) - if services != nil { - err := services(p) - if err != nil { - return err - } + defer pp.Remove(p) + if services == nil { + return nil } - return nil + return services(p) } - return newBzzBaseTester(t, n, addr, ct, extraservices) + return newBzzBaseTester(t, n, addr, spec, extraservices) } // should test handshakes in one exchange? parallelisation func (s *bzzTester) testHandshake(lhs, rhs *bzzHandshake, disconnects ...*p2ptest.Disconnect) { var peers []*adapters.NodeId - id := NodeId(rhs.Addr) + id := NewNodeIdFromAddr(rhs.Addr) if len(disconnects) > 0 { for _, d := range disconnects { peers = append(peers, d.Peer) @@ -106,14 +131,18 @@ func (s *bzzTester) runHandshakes(ids ...*adapters.NodeId) { ids = s.Ids } for _, id := range ids { - s.testHandshake(correctBzzHandshake(s.addr), correctBzzHandshake(NewPeerAddrFromNodeId(id))) + s.testHandshake(correctBzzHandshake(s.addr), correctBzzHandshake(NewAddrFromNodeId(id))) <-s.cs[id.NodeID.String()] } } -func correctBzzHandshake(addr *peerAddr) *bzzHandshake { - return &bzzHandshake{0, 322, addr} +func correctBzzHandshake(addr *bzzAddr) *bzzHandshake { + return &bzzHandshake{ + Version: 0, + NetworkId: 322, + Addr: addr, + } } func TestBzzHandshakeNetworkIdMismatch(t *testing.T) { @@ -125,7 +154,7 @@ func TestBzzHandshakeNetworkIdMismatch(t *testing.T) { id := s.Ids[0] s.testHandshake( correctBzzHandshake(addr), - &bzzHandshake{0, 321, NewPeerAddrFromNodeId(id)}, + &bzzHandshake{Version: 0, NetworkId: 321, Addr: NewAddrFromNodeId(id)}, &p2ptest.Disconnect{Peer: id, Error: fmt.Errorf("network id mismatch 321 (!= 322)")}, ) } @@ -139,7 +168,7 @@ func TestBzzHandshakeVersionMismatch(t *testing.T) { id := s.Ids[0] s.testHandshake( correctBzzHandshake(addr), - &bzzHandshake{1, 322, NewPeerAddrFromNodeId(id)}, + &bzzHandshake{Version: 1, NetworkId: 322, Addr: NewAddrFromNodeId(id)}, &p2ptest.Disconnect{Peer: id, Error: fmt.Errorf("version mismatch 1 (!= 0)")}, ) } @@ -153,70 +182,6 @@ func TestBzzHandshakeSuccess(t *testing.T) { id := s.Ids[0] s.testHandshake( correctBzzHandshake(addr), - &bzzHandshake{0, 322, NewPeerAddrFromNodeId(id)}, + &bzzHandshake{Version: 0, NetworkId: 322, Addr: NewAddrFromNodeId(id)}, ) } - -func TestBzzPeerPoolAdd(t *testing.T) { - pp := p2ptest.NewTestPeerPool() - addr := RandomAddr() - s := newBzzTester(t, 1, addr, pp, nil, nil) - defer s.Stop() - - id := s.Ids[0] - log.Trace(fmt.Sprintf("handshake with %v", id)) - s.runHandshakes() - - if !pp.Has(id) { - t.Fatalf("peer '%v' not added: %v", id, pp) - } -} - -func TestBzzPeerPoolRemove(t *testing.T) { - addr := RandomAddr() - pp := p2ptest.NewTestPeerPool() - s := newBzzTester(t, 1, addr, pp, nil, nil) - defer s.Stop() - - s.runHandshakes() - - id := s.Ids[0] - pp.Get(id).Drop(fmt.Errorf("p2p: read or write on closed message pipe")) - s.TestDisconnected(&p2ptest.Disconnect{id, fmt.Errorf("p2p: read or write on closed message pipe")}) - if pp.Has(id) { - t.Fatalf("peer '%v' not removed: %v", id, pp) - } -} - -func TestBzzPeerPoolBothAddRemove(t *testing.T) { - addr := RandomAddr() - pp := p2ptest.NewTestPeerPool() - s := newBzzTester(t, 1, addr, pp, nil, nil) - defer s.Stop() - - s.runHandshakes() - - id := s.Ids[0] - if !pp.Has(id) { - t.Fatalf("peer '%v' not added: %v", id, pp) - } - - pp.Get(id).Drop(fmt.Errorf("p2p: read or write on closed message pipe")) - s.TestDisconnected(&p2ptest.Disconnect{Peer: id, Error: fmt.Errorf("p2p: read or write on closed message pipe")}) - if pp.Has(id) { - t.Fatalf("peer '%v' not removed: %v", id, pp) - } -} - -func TestBzzPeerPoolNotAdd(t *testing.T) { - addr := RandomAddr() - pp := p2ptest.NewTestPeerPool() - s := newBzzTester(t, 1, addr, pp, nil, nil) - defer s.Stop() - - id := s.Ids[0] - s.testHandshake(correctBzzHandshake(addr), &bzzHandshake{0, 321, NewPeerAddrFromNodeId(id)}, &p2ptest.Disconnect{Peer: id, Error: fmt.Errorf("network id mismatch 321 (!= 322)")}) - if pp.Has(id) { - t.Fatalf("peer %v incorrectly added: %v", id, pp) - } -} diff --git a/swarm/network/pssapi.go b/swarm/network/pssapi.go deleted file mode 100644 index 82b45e8b03..0000000000 --- a/swarm/network/pssapi.go +++ /dev/null @@ -1,66 +0,0 @@ -package network - -import ( - "context" - "fmt" - - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rpc" -) - -type PssApi struct { - *Pss -} - -func NewPssApi(ps *Pss) *PssApi { - return &PssApi{Pss: ps} -} - -func (self *PssApi) NewMsg(ctx context.Context, topic PssTopic) (*rpc.Subscription, error) { - notifier, supported := rpc.NotifierFromContext(ctx) - if !supported { - return nil, fmt.Errorf("Subscribe not supported") - } - - sub := notifier.CreateSubscription() - - ch := make(chan []byte) - psssub, err := self.Pss.Subscribe(&topic, ch) - if err != nil { - return nil, fmt.Errorf("pss subscription topic %v (rpc sub id %v) failed: %v", topic, sub.ID, err) - } - - go func(topic PssTopic) error { - for { - select { - case msg := <-ch: - if err := notifier.Notify(sub.ID, msg); err != nil { - log.Warn(fmt.Sprintf("notification on pss sub topic %v rpc (sub %v) msg %v failed!", topic, sub.ID, msg)) - return err - } - case err := <-psssub.Err(): - log.Warn(fmt.Sprintf("caught subscription error in pss sub topic: %v", topic, err)) - return err - case <-notifier.Closed(): - log.Warn(fmt.Sprintf("rpc sub notifier closed")) - psssub.Unsubscribe() - return nil - case err := <-sub.Err(): - log.Warn(fmt.Sprintf("rpc sub closed: %v", err)) - psssub.Unsubscribe() - return nil - } - } - return nil - }(topic) - - return sub, nil -} - -func (self *PssApi) SendRaw(to []byte, topic PssTopic, msg []byte) error { - err := self.Pss.Send(to, topic, msg) - if err != nil { - return fmt.Errorf("send error: %v", err) - } - return fmt.Errorf("ok sent") -} diff --git a/swarm/network/simulations/discovery/discovery_test.go b/swarm/network/simulations/discovery/discovery_test.go index 22d97efec0..a02e1d67f3 100644 --- a/swarm/network/simulations/discovery/discovery_test.go +++ b/swarm/network/simulations/discovery/discovery_test.go @@ -9,22 +9,20 @@ import ( "time" "github.com/ethereum/go-ethereum/log" - p2pnode "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/simulations" "github.com/ethereum/go-ethereum/p2p/simulations/adapters" - "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/swarm/network" ) // serviceName is used with the exec adapter so the exec'd binary knows which // service to execute const serviceName = "discovery" +const testMinProxBinSize = 2 var services = adapters.Services{ - serviceName: func(id *adapters.NodeId, snapshot []byte) p2pnode.Service { - return newNode(id) - }, + serviceName: newService, } func init() { @@ -70,7 +68,7 @@ func testDiscoverySimulation(t *testing.T, adapter adapters.NodeAdapter) { for i := 0; i < nodeCount; i++ { node, err := net.NewNode() if err != nil { - t.Fatalf("error starting node %s: %s", node.ID().Label(), err) + t.Fatalf("error starting node: %s", err) } if err := net.Start(node.ID()); err != nil { t.Fatalf("error starting node %s: %s", node.ID().Label(), err) @@ -97,6 +95,7 @@ func testDiscoverySimulation(t *testing.T, adapter adapters.NodeAdapter) { } return nil } + nnmap := network.NewPeerPot(testMinProxBinSize, ids...) check := func(ctx context.Context, id *adapters.NodeId) (bool, error) { select { case <-ctx.Done(): @@ -113,7 +112,7 @@ func testDiscoverySimulation(t *testing.T, adapter adapters.NodeAdapter) { return false, fmt.Errorf("error getting node client: %s", err) } var healthy bool - if err := client.Call(&healthy, "hive_healthy", nil); err != nil { + if err := client.Call(&healthy, "hive_healthy", nnmap[id.NodeID]); err != nil { return false, fmt.Errorf("error getting node health: %s", err) } return healthy, nil @@ -179,70 +178,26 @@ func triggerChecks(trigger chan *adapters.NodeId, net *simulations.Network, id * return nil } -type node struct { - *network.Hive +func newService(id *adapters.NodeId, snapshot []byte) node.Service { + addr := network.NewAddrFromNodeId(id) - protocol *p2p.Protocol -} + kp := network.NewKadParams() + kp.MinProxBinSize = testMinProxBinSize + kp.MaxBinSize = 3 + kp.MinBinSize = 1 + kp.MaxRetries = 1000 + kp.RetryExponent = 2 + kp.RetryInterval = 1000000 + kad := network.NewKademlia(addr.Over(), kp) -func newNode(id *adapters.NodeId) *node { - addr := network.NewPeerAddrFromNodeId(id) - kademlia := newKademlia(addr.OverlayAddr()) - hive := newHive(kademlia) - codeMap := network.BzzCodeMap(network.DiscoveryMsgs...) - node := &node{Hive: hive} - services := func(peer network.Peer) error { - discoveryPeer := network.NewDiscovery(peer, kademlia) - node.Add(discoveryPeer) - peer.DisconnectHook(func(err error) { - node.Remove(discoveryPeer) - }) - return nil + hp := network.NewHiveParams() + hp.KeepAliveInterval = time.Second + + config := &network.BzzConfig{ + OverlayAddr: addr.Over(), + UnderlayAddr: addr.Under(), + HiveParams: hp, } - node.protocol = network.Bzz(addr.OverlayAddr(), addr.UnderlayAddr(), codeMap, services, nil, nil) - return node -} - -func newKademlia(overlayAddr []byte) *network.Kademlia { - params := network.NewKadParams() - params.MinProxBinSize = 2 - params.MaxBinSize = 3 - params.MinBinSize = 1 - params.MaxRetries = 1000 - params.RetryExponent = 2 - params.RetryInterval = 1000000 - - return network.NewKademlia(overlayAddr, params) -} - -func newHive(kademlia *network.Kademlia) *network.Hive { - params := network.NewHiveParams() - params.CallInterval = 5000 - - return network.NewHive(params, kademlia) -} - -func (n *node) Protocols() []p2p.Protocol { - return []p2p.Protocol{*n.protocol} -} - -func (n *node) APIs() []rpc.API { - return []rpc.API{{ - Namespace: "hive", - Version: "1.0", - Service: n.Hive, - }} -} - -func (n *node) Start(server p2p.Server) error { - return n.Hive.Start(server, n.hiveKeepAlive) -} - -func (n *node) Stop() error { - n.Hive.Stop() - return nil -} - -func (n *node) hiveKeepAlive() <-chan time.Time { - return time.Tick(time.Second) + + return network.NewBzz(config, kad, nil) } diff --git a/swarm/network/simulations/overlay.go b/swarm/network/simulations/overlay.go index 198de66394..9c7ce159e6 100644 --- a/swarm/network/simulations/overlay.go +++ b/swarm/network/simulations/overlay.go @@ -11,83 +11,58 @@ import ( "net/http" "os" "runtime" + "sync" "time" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/simulations" "github.com/ethereum/go-ethereum/p2p/simulations/adapters" - "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/swarm/network" ) -// SimNode is the adapter used by Swarm simulations. -type SimNode struct { - hive *network.Hive - protocol *p2p.Protocol +type Simulation struct { + mtx sync.Mutex + stores map[discover.NodeID]*adapters.stateStore } -func (s *SimNode) Protocols() []p2p.Protocol { - return []p2p.Protocol{*s.protocol} +func NewSimulation() *Simulation { + return &Simulation{ + stores: make(map[discover.NodeID]*adapters.stateStore), + } } -func (s *SimNode) APIs() []rpc.API { - return nil -} +func (s *Simulation) NewService(id *adapters.NodeId, snapshot []byte) node.Service { + s.mtx.Lock() + store, ok := s.stores[id.NodeID] + if !ok { + store = NewSimStore() + s.stores[id.NodeID] = store + } + s.mtx.Unlock() -// the hive update ticker for hive -func af() <-chan time.Time { - return time.NewTicker(1 * time.Second).C -} + addr := network.NewAddrFromNodeId(id) -// Start() starts up the hive -// makes SimNode implement node.Service -func (self *SimNode) Start(server p2p.Server) error { - return self.hive.Start(server, af) -} - -// Stop() shuts down the hive -// makes SimNode implement node.Service -func (self *SimNode) Stop() error { - self.hive.Stop() - return nil -} - -// NewSimNode creates adapters for nodes in the simulation. -func NewSimNode(id *adapters.NodeId, snapshot []byte) node.Service { - addr := network.NewPeerAddrFromNodeId(id) kp := network.NewKadParams() - kp.MinProxBinSize = 2 - kp.MaxBinSize = 3 - kp.MinBinSize = 1 + kp.MaxBinSize = 8 + kp.MinBinSize = 2 kp.MaxRetries = 1000 kp.RetryExponent = 2 - kp.RetryInterval = 1000000 + kp.RetryInterval = 1000 + kad := network.NewKademlia(addr.Over(), kp) - to := network.NewKademlia(addr.OverlayAddr(), kp) // overlay topology driver hp := network.NewHiveParams() - hp.CallInterval = 5000 - pp := network.NewHive(hp, to) // hive + hp.KeepAliveInterval = 3 * time.Second - services := func(p network.Peer) error { - dp := network.NewDiscovery(p, to) - pp.Add(dp) - log.Trace(fmt.Sprintf("kademlia on %v", dp)) - p.DisconnectHook(func(err error) { - pp.Remove(dp) - }) - return nil + config := &network.BzzConfig{ + OverlayAddr: addr.Over(), + UnderlayAddr: addr.Under(), + HiveParams: hp, } - ct := network.BzzCodeMap(network.DiscoveryMsgs...) // bzz protocol code map - nodeInfo := func() interface{} { return pp.String() } - - return &SimNode{ - hive: pp, - protocol: network.Bzz(addr.OverlayAddr(), addr.UnderlayAddr(), ct, services, nil, nodeInfo), - } + return network.NewBzz(config, kad, store) } func createMockers() map[string]*simulations.MockerConfig { @@ -119,8 +94,9 @@ func setupMocker(net *simulations.Network) []*adapters.NodeId { conf := net.Config() conf.DefaultService = "overlay" - ids := make([]*adapters.NodeId, 10) - for i := 0; i < 10; i++ { + nodeCount := 50 + ids := make([]*adapters.NodeId, nodeCount) + for i := 0; i < nodeCount; i++ { node, err := net.NewNode() if err != nil { panic(err.Error()) @@ -129,8 +105,6 @@ func setupMocker(net *simulations.Network) []*adapters.NodeId { } for _, id := range ids { - n := rand.Intn(1000) - time.Sleep(time.Duration(n) * time.Millisecond) if err := net.Start(id); err != nil { panic(err.Error()) } @@ -143,7 +117,12 @@ func setupMocker(net *simulations.Network) []*adapters.NodeId { } else { peerId = ids[i-1] } - if err := net.Connect(id, peerId); err != nil { + ch := make(chan network.OverlayAddr) + go func() { + defer close(ch) + ch <- network.NewAddrFromNodeId(peerId) + }() + if err := net.GetNode(id).Node.(*adapters.SimNode).Service().(*network.Bzz).Hive.Register(ch); err != nil { panic(err.Error()) } } @@ -191,23 +170,23 @@ func randomMocker(net *simulations.Network) { func startStopMocker(net *simulations.Network) { ids := setupMocker(net) - for i, id := range ids { - n := 3000 + i*1000 - go func(id *adapters.NodeId) { - for { - // n := rand.Intn(5000) - // n := 3000 - time.Sleep(time.Duration(n) * time.Millisecond) - log.Debug(fmt.Sprintf("node %v shutting down", id)) - net.Stop(id) - // n = rand.Intn(5000) - n = 2000 - time.Sleep(time.Duration(n) * time.Millisecond) - log.Debug(fmt.Sprintf("node %v starting up", id)) - net.Start(id) - n = 5000 + for range time.Tick(10 * time.Second) { + id := ids[rand.Intn(len(ids))] + go func() { + log.Error("stopping node", "id", id) + if err := net.Stop(id); err != nil { + log.Error("error stopping node", "id", id, "err", err) + return } - }(id) + + time.Sleep(3 * time.Second) + + log.Error("starting node", "id", id) + if err := net.Start(id); err != nil { + log.Error("error starting node", "id", id, "err", err) + return + } + }() } } @@ -215,10 +194,11 @@ func startStopMocker(net *simulations.Network) { func main() { runtime.GOMAXPROCS(runtime.NumCPU()) - log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(false)))) + log.Root().SetHandler(log.LvlFilterHandler(log.LvlDebug, log.StreamHandler(os.Stderr, log.TerminalFormat(false)))) + s := NewSimulation() services := adapters.Services{ - "overlay": NewSimNode, + "overlay": s.NewService, } adapters.RegisterServices(services) @@ -226,7 +206,7 @@ func main() { config := &simulations.ServerConfig{ NewAdapter: func() adapters.NodeAdapter { return adapters.NewSimAdapter(services) }, - DefaultMockerId: "start-stop", + DefaultMockerId: "bootNet", Mockers: mockers, } diff --git a/swarm/network/test_overlay.go b/swarm/network/test_overlay.go index de839bcbab..ebcbbc3108 100644 --- a/swarm/network/test_overlay.go +++ b/swarm/network/test_overlay.go @@ -1,182 +1,193 @@ package network -import ( - "fmt" - "strings" - "sync" - - "github.com/ethereum/go-ethereum/log" -) - -const orders = 8 - -type testOverlay struct { - mu sync.Mutex - addr []byte - pos [][]*testPeerAddr - posMap map[string]*testPeerAddr -} - -type testPeerAddr struct { - PeerAddr - Peer Peer -} - -func (self *testOverlay) Register(nas ...PeerAddr) error { - self.mu.Lock() - defer self.mu.Unlock() - return self.register(nas...) -} - -func (self *testOverlay) GetAddr() PeerAddr { - return &peerAddr{ - OAddr: self.addr, - UAddr: []byte{}, - } -} - -func (self *testOverlay) register(nas ...PeerAddr) error { - for _, na := range nas { - tna := &testPeerAddr{PeerAddr: na} - addr := na.OverlayAddr() - if self.posMap[string(addr)] != nil { - continue - } - self.posMap[string(addr)] = tna - o := order(addr) - log.Trace(fmt.Sprintf("PO: %v, orders: %v", o, orders)) - self.pos[o] = append(self.pos[o], tna) - } - return nil -} - -func order(addr []byte) int { - return int(addr[0]) / 32 -} - -func (self *testOverlay) On(n Peer) { - self.mu.Lock() - defer self.mu.Unlock() - addr := n.OverlayAddr() - na := self.posMap[string(addr)] - if na == nil { - self.register(n) - na = self.posMap[string(addr)] - } else if na.Peer != nil { - return - } - log.Trace(fmt.Sprintf("Online: %x", addr[:4])) - na.Peer = n - return -} - -func (self *testOverlay) Off(n Peer) { - self.mu.Lock() - defer self.mu.Unlock() - addr := n.OverlayAddr() - na := self.posMap[string(addr)] - if na == nil { - return - } - delete(self.posMap, string(addr)) - na.Peer = nil -} - -// caller must hold the lock -func (self *testOverlay) on(po []*testPeerAddr) (nodes []Peer) { - for _, na := range po { - if na.Peer != nil { - nodes = append(nodes, na.Peer) - } - } - return nodes -} - -// caller must hold the lock -func (self *testOverlay) off(po []*testPeerAddr) (nas []PeerAddr) { - for _, na := range po { - if na.Peer == (*bzzPeer)(nil) { - nas = append(nas, PeerAddr(na)) - } - } - return nas -} - -func (self *testOverlay) EachLivePeer(base []byte, o int, f func(Peer, int, bool) bool) { - if base == nil { - base = self.addr - } - for i := o; i < len(self.pos); i++ { - for _, na := range self.pos[i] { - if na.Peer != nil { - if !f(na.Peer, o, false) { - return - } - } - } - } -} - -func (self *testOverlay) EachPeer(base []byte, o int, f func(PeerAddr, int) bool) { - if base == nil { - base = self.addr - } - for i := o; i < len(self.pos); i++ { - for _, na := range self.pos[i] { - if !f(na, i) { - return - } - } - } -} - -func (self *testOverlay) SuggestPeer() (PeerAddr, int, bool) { - self.mu.Lock() - defer self.mu.Unlock() - for i, po := range self.pos { - ons := self.on(po) - if len(ons) < 2 { - offs := self.off(po) - if len(offs) > 0 { - log.Trace(fmt.Sprintf("node %v is off", offs[0])) - return offs[0], i, true - } - } - } - return nil, 0, true -} - -func (self *testOverlay) String() string { - self.mu.Lock() - defer self.mu.Unlock() - var t []string - var ons, offs int - var ns []Peer - var nas []PeerAddr - for o, po := range self.pos { - var row []string - ns = self.on(po) - nas = self.off(po) - ons = len(ns) - for _, n := range ns { - addr := n.OverlayAddr() - row = append(row, fmt.Sprintf("%x", addr[:4])) - } - row = append(row, "|") - offs = len(nas) - for _, na := range nas { - addr := na.OverlayAddr() - row = append(row, fmt.Sprintf("%x", addr[:4])) - } - t = append(t, fmt.Sprintf("%v: (%v/%v) %v", o, ons, offs, strings.Join(row, " "))) - } - return strings.Join(t, "\n") -} - -func NewTestOverlay(addr []byte) *testOverlay { - return &testOverlay{ - addr: addr, - posMap: make(map[string]*testPeerAddr), - pos: make([][]*testPeerAddr, orders), - } -} +// +// import ( +// "fmt" +// "strings" +// "sync" +// +// "github.com/ethereum/go-ethereum/log" +// ) +// +// const orders = 8 +// +// type testOverlay struct { +// mu sync.Mutex +// addr []byte +// pos [][]OverlayAddr +// posMap map[string]OverlayAddr +// } +// +// type testPeerAddr struct { +// Addr +// Peer +// } +// +// func (self *testPeerAddr) Address() []byte { +// return nil +// } +// +// func (self *testPeerAddr) Update(a OverlayAddr) OverlayAddr { +// return self +// } +// +// func (self *testPeerAddr) On(p OverlayConn) OverlayConn { +// return self +// } +// +// func (self *testPeerAddr) Off() OverlayAddr { +// return self +// } +// +// func (self *testOverlay) Register(peers chan OverlayAddr) error { +// self.mu.Lock() +// defer self.mu.Unlock() +// var nas []OverlayAddr +// for a := range peers { +// nas = append(nas, a) +// } +// return self.register(nas...) +// } +// +// func (self *testOverlay) BaseAddr() []byte { +// return nil +// } +// +// func (self *testOverlay) register(nas ...OverlayAddr) error { +// for _, na := range nas { +// addr := na.Address() +// if self.posMap[string(addr)] != nil { +// continue +// } +// self.posMap[string(addr)] = na +// o := order(addr) +// log.Trace(fmt.Sprintf("PO: %v, orders: %v", o, orders)) +// self.pos[o] = append(self.pos[o], na) +// } +// return nil +// } +// +// func order(addr []byte) int { +// return int(addr[0]) / 32 +// } +// +// func (self *testOverlay) On(n OverlayConn) { +// self.mu.Lock() +// defer self.mu.Unlock() +// addr := n.Address() +// na := self.posMap[string(addr)] +// if na == nil { +// self.register(n) +// na = self.posMap[string(addr)] +// } else if na.Peer != nil { +// return +// } +// log.Trace(fmt.Sprintf("Online: %x", addr[:4])) +// na.Peer = n +// return +// } +// +// func (self *testOverlay) Off(n OverlayConn) { +// self.mu.Lock() +// defer self.mu.Unlock() +// addr := n.Over() +// na := self.posMap[string(addr)] +// if na == nil { +// return +// } +// delete(self.posMap, string(addr)) +// na.Peer = nil +// } +// +// // caller must hold the lock +// func (self *testOverlay) on(po []*testPeerAddr) (nodes []OverlayConn) { +// for _, na := range po { +// if na.Peer != nil { +// nodes = append(nodes, na) +// } +// } +// return nodes +// } +// +// // caller must hold the lock +// func (self *testOverlay) off(po []*testPeerAddr) (nas []OverlayAddr) { +// for _, na := range po { +// if na.Peer == (*bzzPeer)(nil) { +// nas = append(nas, Addr(na)) +// } +// } +// return nas +// } +// +// func (self *testOverlay) EachConn(base []byte, o int, f func(OverlayConn, int, bool) bool) { +// for i := o; i < len(self.pos); i++ { +// for _, na := range self.pos[i] { +// if na.Peer != nil { +// if !f(na, o, false) { +// return +// } +// } +// } +// } +// } +// +// func (self *testOverlay) EachAddr(base []byte, o int, f func(OverlayAddr, int) bool) { +// for i := o; i < len(self.pos); i++ { +// for _, na := range self.pos[i] { +// if !f(na, i) { +// return +// } +// } +// } +// } +// +// func (self *testOverlay) SuggestPeer() (OverlayAddr, int, bool) { +// self.mu.Lock() +// defer self.mu.Unlock() +// for i, po := range self.pos { +// ons := self.on(po) +// if len(ons) < 2 { +// offs := self.off(po) +// if len(offs) > 0 { +// log.Trace(fmt.Sprintf("node %v is off", offs[0])) +// return offs[0], i, true +// } +// } +// } +// return nil, 0, true +// } +// +// func (self *testOverlay) String() string { +// self.mu.Lock() +// defer self.mu.Unlock() +// var t []string +// var ons, offs int +// var ns []Peer +// var nas []Addr +// for o, po := range self.pos { +// var row []string +// ns = self.on(po) +// nas = self.off(po) +// ons = len(ns) +// for _, n := range ns { +// addr := n.Over() +// row = append(row, fmt.Sprintf("%x", addr[:4])) +// } +// row = append(row, "|") +// offs = len(nas) +// for _, na := range nas { +// addr := na.Over() +// row = append(row, fmt.Sprintf("%x", addr[:4])) +// } +// t = append(t, fmt.Sprintf("%v: (%v/%v) %v", o, ons, offs, strings.Join(row, " "))) +// } +// return strings.Join(t, "\n") +// } +// +// func NewTestOverlay(addr []byte) *testOverlay { +// return &testOverlay{ +// addr: addr, +// posMap: make(map[string]*testPeerAddr), +// pos: make([][]*testPeerAddr, orders), +// } +// } diff --git a/swarm/pss/client.go b/swarm/pss/client.go new file mode 100644 index 0000000000..d71d7928a9 --- /dev/null +++ b/swarm/pss/client.go @@ -0,0 +1,228 @@ +package pss + +import ( + "context" + "fmt" + "sync" + "sync/atomic" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/discover" + "github.com/ethereum/go-ethereum/p2p/protocols" + "github.com/ethereum/go-ethereum/pot" + "github.com/ethereum/go-ethereum/rpc" +) + +const ( + inboxCapacity = 3000 + outboxCapacity = 100 + addrLen = common.HashLength +) + +// implements p2p.Server +// implements net.Conn +type PssClient struct { + localuri string + remoteuri string + ctx context.Context + cancel func() + subscription *rpc.ClientSubscription + topicsC chan []byte + msgC chan PssAPIMsg + quitC chan struct{} + quitting uint32 + ws *rpc.Client + lock sync.Mutex + peerPool map[PssTopic]map[pot.Address]*pssRPCRW + protos []*p2p.Protocol +} + +type pssRPCRW struct { + *PssClient + topic *PssTopic + spec *protocols.Spec + msgC chan []byte + addr pot.Address +} + +func (self *PssClient) newpssRPCRW(addr pot.Address, spec *protocols.Spec, topic *PssTopic) *pssRPCRW { + return &pssRPCRW { + PssClient: self, + topic: topic, + spec: spec, + msgC: make(chan []byte), + addr: addr, + } +} + +func (rw *pssRPCRW) ReadMsg() (p2p.Msg, error) { + msg := <- rw.msgC + log.Warn("pssrpcrw read", "msg", msg) + pmsg, err := ToP2pMsg(msg) + if err != nil { + return p2p.Msg{}, err + } + + return pmsg, nil +} + +func (rw *pssRPCRW) WriteMsg(msg p2p.Msg) error { + + ifc, found := rw.spec.NewMsg(msg.Code) + if !found { + return fmt.Errorf("could not find interface for msg #%d", msg.Code) + } + msg.Decode(ifc) + pmsg, err := newProtocolMsg(msg.Code, ifc) + if err != nil { + return fmt.Errorf("Could not render protocolmessage", "error", err) + } + + return rw.PssClient.ws.CallContext(rw.PssClient.ctx, nil, "pss_sendRaw", rw.topic, PssAPIMsg{ + Addr: rw.addr.Bytes(), + Msg: pmsg, + }) + +} + +// remotehost: hostname of node running websockets proxy to pss (default localhost) +// remoteport: port of node running websockets proxy to pss (0 = go-ethereum node default) +// secure: whether or not to use secure connection +// originhost: local if host to connect from + +func NewPssClient(ctx context.Context, cancel func(), remotehost string, remoteport int, secure bool, originhost string) *PssClient { + prefix := "ws" + + if ctx == nil { + ctx = context.Background() + cancel = func() {return} + } + pssc := &PssClient{ + msgC: make(chan PssAPIMsg), + quitC: make(chan struct{}), + peerPool: make(map[PssTopic]map[pot.Address]*pssRPCRW), + ctx: ctx, + cancel: cancel, + } + + if remotehost == "" { + remotehost = "localhost" + } + + if remoteport == 0 { + remoteport = node.DefaultWSPort + } + + if originhost == "" { + originhost = "localhost" + } + + if secure { + prefix = "wss" + } + + pssc.remoteuri = fmt.Sprintf("%s://%s:%d", prefix, remotehost, remoteport) + pssc.localuri = fmt.Sprintf("%s://%s", prefix, originhost) + + return pssc +} + +func (self *PssClient) shutdown() { + atomic.StoreUint32(&self.quitting, 1) + self.cancel() +} + +func (self *PssClient) Start() error { + log.Debug("Dialing ws", "src", self.localuri, "dst", self.remoteuri) + ws, err := rpc.DialWebsocket(self.ctx, self.remoteuri, self.localuri) + if err != nil { + return fmt.Errorf("Couldnt dial pss websocket: %v", err) + } + + self.ws = ws + + return nil +} + +func (self *PssClient) RunProtocol(proto *p2p.Protocol, spec *protocols.Spec) error { + topic := NewTopic(spec.Name, int(spec.Version)) + msgC := make(chan PssAPIMsg) + self.peerPool[topic] = make(map[pot.Address]*pssRPCRW) + sub, err := self.ws.Subscribe(self.ctx, "pss", msgC, "newMsg", topic) + if err != nil { + return fmt.Errorf("pss event subscription failed: %v", err) + } + + self.subscription = sub + + // dispatch incoming messages + go func() { + for { + select { + case msg := <- msgC: + var addr pot.Address + copy(addr[:], msg.Addr) + if self.peerPool[topic][addr] == nil { + self.peerPool[topic][addr] = self.newpssRPCRW(addr, spec, &topic) + nid, _ := discover.HexID("0x00") + p := p2p.NewPeer(nid, fmt.Sprintf("%v", addr), []p2p.Cap{}) + go proto.Run(p, self.peerPool[topic][addr]) + } + go func() { + self.peerPool[topic][addr].msgC <- msg.Msg + }() + case <-self.quitC: + self.shutdown() + return + } + } + }() + + self.protos = append(self.protos, proto) + return nil +} + +func (self *PssClient) Stop() error { + self.cancel() + return nil +} + +func (self *PssClient) AddPssPeer(addr pot.Address, spec *protocols.Spec) { + topic := NewTopic(spec.Name, int(spec.Version)) + if self.peerPool[topic][addr] == nil { + self.peerPool[topic][addr] = self.newpssRPCRW(addr, spec, &topic) + } +} + +func (self *PssClient) RemovePssPeer(addr pot.Address, spec *protocols.Spec) { + topic := NewTopic(spec.Name, int(spec.Version)) + delete(self.peerPool[topic], addr) +} + +func (self *PssClient) SubscribeEvents(ch chan *p2p.PeerEvent) event.Subscription { + log.Error("PSS client handles events internally, use the read functions instead") + return nil +} + +func (self *PssClient) PeerCount() int { + return len(self.peerPool) +} + +func (self *PssClient) NodeInfo() *p2p.NodeInfo { + return nil +} + +func (self *PssClient) PeersInfo() []*p2p.PeerInfo { + return nil +} +func (self *PssClient) AddPeer(node *discover.Node) { + log.Error("Cannot add peer in PSS with discover.Node, need swarm overlay address") +} + +func (self *PssClient) RemovePeer(node *discover.Node) { + log.Error("Cannot remove peer in PSS with discover.Node, need swarm overlay address") +} diff --git a/swarm/pss/client_test.go b/swarm/pss/client_test.go new file mode 100644 index 0000000000..05fcd2f542 --- /dev/null +++ b/swarm/pss/client_test.go @@ -0,0 +1,173 @@ +package pss + +import ( + "context" + "fmt" + "os" + "net" + "net/http" + "testing" + "time" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/discover" + "github.com/ethereum/go-ethereum/p2p/protocols" + "github.com/ethereum/go-ethereum/pot" + "github.com/ethereum/go-ethereum/rpc" +) + +func init() { + h := log.CallerFileHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(true))) + log.Root().SetHandler(h) +} + +func TestRunProtocol(t *testing.T) { + quitC := make(chan struct{}) + pss := newTestPss(nil) + ping := &pssPing{ + quitC: make(chan struct{}), + } + proto := newProtocol(ping) + _, err := baseTester(t, proto, pss, nil, nil, quitC) + if err != nil { + t.Fatalf(err.Error()) + } + quitC <- struct{}{} +} + +func TestIncoming(t *testing.T) { + quitC := make(chan struct{}) + pss := newTestPss(nil) + ctx, cancel := context.WithCancel(context.Background()) + var addr []byte + ping := &pssPing{ + quitC: make(chan struct{}), + } + proto := newProtocol(ping) + client, err := baseTester(t, proto, pss, ctx, cancel, quitC) + if err != nil { + t.Fatalf(err.Error()) + } + + client.ws.Call(&addr, "pss_baseAddr") + + code, _ := pssPingProtocol.GetCode(&pssPingMsg{}) + rlpbundle, err := newProtocolMsg(code, &pssPingMsg{ + Created: time.Now(), + }) + if err != nil { + t.Fatalf("couldn't make pssmsg") + } + + pssenv := PssEnvelope{ + From: addr, + Topic: NewTopic(proto.Name, int(proto.Version)), + TTL: DefaultTTL, + Payload: rlpbundle, + } + pssmsg := PssMsg{ + To: addr, + Payload: &pssenv, + } + + pss.Process(&pssmsg) + + go func() { + <-ping.quitC + client.cancel() + }() + + <-client.ctx.Done() + quitC <- struct{}{} +} + +func TestOutgoing(t *testing.T) { + quitC := make(chan struct{}) + pss := newTestPss(nil) + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond * 250) + var addr []byte + var potaddr pot.Address + + ping := &pssPing{ + quitC: make(chan struct{}), + } + proto := newProtocol(ping) + client, err := baseTester(t, proto, pss, ctx, cancel, quitC) + if err != nil { + t.Fatalf(err.Error()) + } + + client.ws.Call(&addr, "pss_baseAddr") + copy(potaddr[:], addr) + + msg := &pssPingMsg{ + Created: time.Now(), + } + + topic := NewTopic(pssPingProtocol.Name, int(pssPingProtocol.Version)) + client.AddPssPeer(potaddr, pssPingProtocol) + nid, _ := discover.HexID("0x00") + p := p2p.NewPeer(nid, fmt.Sprintf("%v", potaddr), []p2p.Cap{}) + pp := protocols.NewPeer(p, client.peerPool[topic][potaddr], pssPingProtocol) + pp.Send(msg) + <-client.ctx.Done() + quitC <- struct{}{} +} + +func baseTester(t *testing.T, proto *p2p.Protocol, pss *Pss, ctx context.Context, cancel func(), quitC chan struct{}) (*PssClient, error) { + var err error + + client := newClient(t, pss, ctx, cancel, quitC) + + err = client.Start() + if err != nil { + return nil, err + } + + err = client.RunProtocol(proto, pssPingProtocol) + + if err != nil { + return nil, err + } + + return client, nil +} + +func newProtocol(ping *pssPing) *p2p.Protocol { + + return &p2p.Protocol{ + Name: pssPingProtocol.Name, + Version: pssPingProtocol.Version, + Length: 1, + Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { + pp := protocols.NewPeer(p, rw, pssPingProtocol) + pp.Run(ping.pssPingHandler) + return nil + }, + } +} + +func newClient(t *testing.T, pss *Pss, ctx context.Context, cancel func(), quitC chan struct{}) *PssClient { + pssclient := NewPssClient(ctx, cancel, "", 0, false, "") + + srv := rpc.NewServer() + srv.RegisterName("pss", NewPssAPI(pss)) + ws := srv.WebsocketHandler([]string{"*"}) + uri := fmt.Sprintf("%s:%d", node.DefaultWSHost, node.DefaultWSPort) + + sock, err := net.Listen("tcp", uri) + if err != nil { + t.Fatalf("Tcp (recv) on %s failed: %v", uri, err) + } + + go func() { + http.Serve(sock, ws) + }() + + go func() { + <-quitC + sock.Close() + }() + return pssclient +} diff --git a/swarm/pss/common.go b/swarm/pss/common.go new file mode 100644 index 0000000000..c3f03698ec --- /dev/null +++ b/swarm/pss/common.go @@ -0,0 +1,125 @@ +package pss + +import ( + "fmt" + "io/ioutil" + "os" + "time" + + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/protocols" + "github.com/ethereum/go-ethereum/swarm/network" + "github.com/ethereum/go-ethereum/swarm/storage" +) + +type pssPingMsg struct { + Created time.Time +} + +type pssPing struct { + quitC chan struct{} +} + +func (self *pssPing) pssPingHandler(msg interface{}) error { + log.Warn("got ping", "msg", msg) + self.quitC <- struct{}{} + return nil +} + +var pssPingProtocol = &protocols.Spec{ + Name: "psstest", + Version: 1, + MaxMsgSize: 10 * 1024 * 1024, + Messages: []interface{}{ + pssPingMsg{}, + }, +} + +var pssPingTopic = NewTopic(pssPingProtocol.Name, int(pssPingProtocol.Version)) + +func newTestPss(addr []byte) *Pss { + if addr == nil { + addr = network.RandomAddr().OAddr + } + + // set up storage + cachedir, err := ioutil.TempDir("", "pss-cache") + if err != nil { + log.Error("create pss cache tmpdir failed", "error", err) + os.Exit(1) + } + dpa, err := storage.NewLocalDPA(cachedir) + if err != nil { + log.Error("local dpa creation failed", "error", err) + os.Exit(1) + } + + // set up routing + kp := network.NewKadParams() + kp.MinProxBinSize = 3 + + // create pss + pp := NewPssParams() + + overlay := network.NewKademlia(addr, kp) + ps := NewPss(overlay, dpa, pp) + + return ps +} + +func newPssPingMsg(ps *Pss, to []byte, spec *protocols.Spec, topic PssTopic, senderaddr []byte) PssMsg { + data := pssPingMsg{ + Created: time.Now(), + } + code, found := spec.GetCode(&data) + if !found { + return PssMsg{} + } + + rlpbundle, err := newProtocolMsg(code, data) + if err != nil { + return PssMsg{} + } + + pssmsg := PssMsg{ + To: to, + Payload: NewPssEnvelope(senderaddr, topic, rlpbundle), + } + + return pssmsg +} + +func newPssPingProtocol(handler func (interface{}) error) *p2p.Protocol { + return &p2p.Protocol{ + Name: pssPingProtocol.Name, + Version: pssPingProtocol.Version, + Length: uint64(pssPingProtocol.MaxMsgSize), + Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { + pp := protocols.NewPeer(p, rw, pssPingProtocol) + log.Trace(fmt.Sprintf("running pss vprotocol on peer %v", p)) + err := pp.Run(handler) + return err + }, + } +} + +type testPssPeer struct { + *protocols.Peer + addr []byte +} + +func (self *testPssPeer) Address() []byte { + return self.addr +} + +func (self *testPssPeer) Off() network.OverlayAddr { + return self +} + +func (self *testPssPeer) Drop(err error) { +} + +func (self *testPssPeer) Update(o network.OverlayAddr) network.OverlayAddr { + return self +} diff --git a/swarm/network/pss.go b/swarm/pss/pss.go similarity index 50% rename from swarm/network/pss.go rename to swarm/pss/pss.go index 3bd5e8d3ee..36873fcee0 100644 --- a/swarm/network/pss.go +++ b/swarm/pss/pss.go @@ -1,4 +1,4 @@ -package network +package pss import ( "bytes" @@ -9,13 +9,16 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/crypto/sha3" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/protocols" + "github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/simulations/adapters" + "github.com/ethereum/go-ethereum/p2p/protocols" "github.com/ethereum/go-ethereum/pot" "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/swarm/network" "github.com/ethereum/go-ethereum/swarm/storage" ) @@ -25,19 +28,20 @@ const ( TopicResolverLength = 8 PssPeerCapacity = 256 PssPeerTopicDefaultCapacity = 8 - digestLength = 64 + digestLength = 32 digestCapacity = 256 defaultDigestCacheTTL = time.Second - pingTopicName = "pss" - pingTopicVersion = 1 ) var ( - errorNoForwarder = errors.New("no available forwarders in routing table") errorForwardToSelf = errors.New("forward to self") - errorBlockByCache = errors.New("message found in blocking cache") ) +type senderPeer interface { + Address() []byte + Send(interface{}) error +} + // Defines params for Pss type PssParams struct { Cachettl time.Duration @@ -51,50 +55,14 @@ func NewPssParams() *PssParams { } // Encapsulates the message transported over pss. -// -// Warning: do not access the To-member directly. Use *PssMsg.GetRecipient() and *PssMsg.SetRecipient() instead. type PssMsg struct { - // (we need the To-member exported for type inference) To []byte - Payload pssEnvelope -} - -// Retrieve the remote peer receipient address of the message -func (self *PssMsg) GetRecipient() []byte { - return self.To -} - -// Set the remote peer recipient address of the message -func (self *PssMsg) SetRecipient(to []byte) { - self.To = to + Payload *PssEnvelope } // String representation of PssMsg func (self *PssMsg) String() string { - return fmt.Sprintf("PssMsg: Recipient: %x", common.ByteLabel(self.GetRecipient())) -} - -// Pre-Whisper placeholder -type pssEnvelope struct { - Topic PssTopic - TTL uint16 - Payload []byte - SenderOAddr []byte - SenderUAddr []byte -} - -// Pre-Whisper placeholder -type pssPayload struct { - Code uint64 - Size uint32 - Data []byte - ReceivedAt time.Time -} - -// Pre-Whisper placeholder -type pssCacheEntry struct { - expiresAt time.Time - receivedFrom []byte + return fmt.Sprintf("PssMsg: Recipient: %x", common.ByteLabel(self.To)) } // Topic defines the context of a message being transported over pss @@ -102,14 +70,70 @@ type pssCacheEntry struct { // Typically, one can map protocol handlers for the message payloads by mapping topic to them; see *Pss.Register() type PssTopic [TopicLength]byte -// Pre-Whisper placeholder -type pssDigest uint32 +func (self *PssTopic) String() string { + return fmt.Sprintf("%x", self) +} + +// Pre-Whisper placeholder, payload of PssMsg +type PssEnvelope struct { + Topic PssTopic + TTL uint16 + Payload []byte + From []byte +} + +// creates Pss envelope from sender address, topic and raw payload +func NewPssEnvelope(addr []byte, topic PssTopic, payload []byte) *PssEnvelope { + return &PssEnvelope{ + From: addr, + Topic: topic, + TTL: DefaultTTL, + Payload: payload, + } +} + + +func (msg *PssMsg) serialize() []byte { + rlpdata, _ := rlp.EncodeToBytes(msg) + /*buf := bytes.NewBuffer(nil) + buf.Write(self.PssEnvelope.Topic[:]) + buf.Write(self.PssEnvelope.Payload) + buf.Write(self.PssEnvelope.From) + return buf.Bytes()*/ + return rlpdata +} + + +var pssSpec = &protocols.Spec{ + Name: "pss", + Version: 1, + MaxMsgSize: 10 * 1024 * 1024, + Messages: []interface{}{ + PssMsg{}, + }, +} + +// encapsulates a protocol msg as PssEnvelope data +type PssProtocolMsg struct { + Code uint64 + Size uint32 + Payload []byte + ReceivedAt time.Time +} + +type pssCacheEntry struct { + expiresAt time.Time + receivedFrom []byte +} + +type pssDigest [digestLength]byte + +// Message handler func for a topic +type pssHandler func(msg []byte, p *p2p.Peer, from []byte) error // pss provides sending messages to nodes without having to be directly connected to them. // // The messages are wrapped in a PssMsg structure and routed using the swarm kademlia routing. -// The structure is used by normal incoming message handlers on the nodes to determine which action to take, forward or process. -// Thus it is up to the implementer to write a handler, and link the PssMsg to this appropriate handler. // // The top-level Pss object provides: // @@ -120,51 +144,118 @@ type pssDigest uint32 // - a dispatcher lookup, mapping protocols to topics // - a message cache to spot messages that previously have been forwarded type Pss struct { - Overlay // we can get the overlayaddress from this - //peerPool map[pot.Address]map[PssTopic]*PssReadWriter // keep track of all virtual p2p.Peers we are currently speaking to - peerPool map[pot.Address]map[PssTopic]p2p.MsgReadWriter // keep track of all virtual p2p.Peers we are currently speaking to - handlers map[PssTopic]func([]byte, *p2p.Peer, []byte) error // topic and version based pss payload handlers - events map[PssTopic]*event.Feed // subscriptions for each topic - fwdcache map[pssDigest]pssCacheEntry // checksum of unique fields from pssmsg mapped to expiry, cache to determine whether to drop msg - cachettl time.Duration // how long to keep messages in fwdcache - hasher func(string) storage.Hasher // hasher to digest message to cache + network.Overlay // we can get the overlayaddress from this + peerPool map[pot.Address]map[PssTopic]p2p.MsgReadWriter // keep track of all virtual p2p.Peers we are currently speaking to + handlers map[PssTopic]map[*pssHandler]bool // topic and version based pss payload handlers + fwdcache map[pssDigest]pssCacheEntry // checksum of unique fields from pssmsg mapped to expiry, cache to determine whether to drop msg + cachettl time.Duration // how long to keep messages in fwdcache lock sync.Mutex + dpa *storage.DPA } -func (self *Pss) hashMsg(msg *PssMsg) pssDigest { - hasher := self.hasher("SHA3")() - hasher.Reset() - hasher.Write(msg.GetRecipient()) - hasher.Write(msg.Payload.SenderUAddr) - hasher.Write(msg.Payload.SenderOAddr) - hasher.Write(msg.Payload.Topic[:]) - hasher.Write(msg.Payload.Payload) - b := hasher.Sum([]byte{}) - return pssDigest(binary.BigEndian.Uint32(b)) +func (self *Pss) storeMsg(msg *PssMsg) (pssDigest, error) { + swg := &sync.WaitGroup{} + wwg := &sync.WaitGroup{} + buf := bytes.NewReader(msg.serialize()) + key, err := self.dpa.Store(buf, int64(buf.Len()), swg, wwg) + if err != nil { + log.Warn("Could not store in swarm", "err", err) + return pssDigest{}, err + } + log.Trace("Stored msg in swarm", "key", key) + digest := pssDigest{} + copy(digest[:], key[:digestLength]) + return digest, nil } // Creates a new Pss instance. A node should only need one of these // -// TODO error check overlay integrity -func NewPss(k Overlay, params *PssParams) *Pss { +// TODO: error check overlay integrity +func NewPss(k network.Overlay, dpa *storage.DPA, params *PssParams) *Pss { return &Pss{ Overlay: k, - //peerPool: make(map[pot.Address]map[PssTopic]*PssReadWriter, PssPeerCapacity), peerPool: make(map[pot.Address]map[PssTopic]p2p.MsgReadWriter, PssPeerCapacity), - handlers: make(map[PssTopic]func([]byte, *p2p.Peer, []byte) error), - events: make(map[PssTopic]*event.Feed), + handlers: make(map[PssTopic]map[*pssHandler]bool), fwdcache: make(map[pssDigest]pssCacheEntry), cachettl: params.Cachettl, - hasher: storage.MakeHashFunc, + dpa: dpa, } } +func (self *Pss) Start(srv *p2p.Server) error { + return nil +} + +func (self *Pss) Stop() error { + return nil +} + +func (self *Pss) Protocols() []p2p.Protocol { + return []p2p.Protocol{ + p2p.Protocol{ + Name: pssSpec.Name, + Version: pssSpec.Version, + Length: pssSpec.Length(), + Run: self.Run, + }, + } +} + +func (self *Pss) Run(p *p2p.Peer, rw p2p.MsgReadWriter) error { + pp := protocols.NewPeer(p, rw, pssSpec) + return pp.Run(self.handlePssMsg) +} + +func (self *Pss) APIs() []rpc.API { + return []rpc.API{ + rpc.API { + Namespace: "pss", + Version: "0.1", + Service: NewPssAPI(self), + Public: true, + }, + } +} + +// Takes the generated PssTopic of a protocol/chatroom etc, and links a handler function to it +// This allows the implementer to retrieve the right handler functions (invoke the right protocol) +// for an incoming message by inspecting the topic on it. +// a topic allows for multiple handlers +// returns a deregister function which needs to be called to deregister the handler +// (similar to event.Subscription.Unsubscribe()) +func (self *Pss) Register(topic *PssTopic, handler pssHandler) func() { + self.lock.Lock() + defer self.lock.Unlock() + handlers := self.handlers[*topic] + if handlers == nil { + handlers = make(map[*pssHandler]bool) + self.handlers[*topic] = handlers + } + handlers[&handler] = true + return func() { self.deregister(topic, &handler) } +} + +func (self *Pss) deregister(topic *PssTopic, h *pssHandler) { + self.lock.Lock() + defer self.lock.Unlock() + handlers := self.handlers[*topic] + if len(handlers) == 1 { + delete(self.handlers, *topic) + return + } + delete(handlers, h) +} + // enables to set address of node, to avoid backwards forwarding // // currently not in use as forwarder address is not known in the handler function hooked to the pss dispatcher. // it is included as a courtesy to custom transport layers that may want to implement this func (self *Pss) AddToCache(addr []byte, msg *PssMsg) error { - digest := self.hashMsg(msg) + //digest := self.hashMsg(msg) + digest, err := self.storeMsg(msg) + if err != nil { + return err + } return self.addFwdCacheSender(addr, digest) } @@ -210,35 +301,115 @@ func (self *Pss) checkFwdCache(addr []byte, digest pssDigest) bool { return false } -// Takes the generated PssTopic of a protocol, and links a handler function to it -// This allows the implementer to retrieve the right handler function (invoke the right protocol) for an incoming message by inspecting the topic on it. -func (self *Pss) Register(topic PssTopic, handler func(msg []byte, p *p2p.Peer, from []byte) error) error { - self.lock.Lock() - defer self.lock.Unlock() - self.handlers[topic] = func(msg []byte, p *p2p.Peer, from []byte) error { - self.alertSubscribers(&topic, msg) - return handler(msg, p, from) - } - self.registerFeed(topic) - return nil -} - -func (self *Pss) Subscribe(topic *PssTopic, ch chan []byte) (event.Subscription, error) { - _, ok := self.events[*topic] - if !ok { - return nil, fmt.Errorf("No feed registered for topic %v", topic) - } - sub := self.events[*topic].Subscribe(ch) - log.Trace("new pss subscribe", "topic", topic, "sub", sub) - return sub, nil -} - -func (self *Pss) GetHandler(topic PssTopic) func([]byte, *p2p.Peer, []byte) error { +func (self *Pss) getHandlers(topic PssTopic) map[*pssHandler]bool { self.lock.Lock() defer self.lock.Unlock() return self.handlers[topic] } +// +func (self *Pss) handlePssMsg(msg interface{}) error { + pssmsg := msg.(*PssMsg) + + if !self.isSelfRecipient(pssmsg) { + log.Trace("pss was for someone else :'( ... forwarding") + return self.Forward(pssmsg) + } + log.Trace("pss for us, yay! ... let's process!") + return self.Process(pssmsg) +} + +// processes a message with self as recipient +func (self *Pss) Process(pssmsg *PssMsg) error { + env := pssmsg.Payload + payload := env.Payload + handlers := self.getHandlers(env.Topic) + if len(handlers) == 0 { + return fmt.Errorf("No registered handler for topic '%s'", env.Topic) + } + nid, _ := discover.HexID("0x00") + p := p2p.NewPeer(nid, fmt.Sprintf("%x", env.From), []p2p.Cap{}) + for f := range handlers { + err := (*f)(payload, p, env.From) + if err != nil { + return err + } + } + return nil +} + +// Sends a message using pss. The message could be anything at all, and will be handled by whichever handler function is mapped to PssTopic using *Pss.Register() +// +// The to address is a swarm overlay address +func (self *Pss) Send(to []byte, topic PssTopic, msg []byte) error { + sender := self.Overlay.BaseAddr() + pssenv := NewPssEnvelope(sender, topic, msg) + pssmsg := &PssMsg{ + To: to, + Payload: pssenv, + } + return self.Forward(pssmsg) +} + +// Forwards a pss message to the peer(s) closest to the to address +// +// Handlers that want to pass on a message should call this directly +func (self *Pss) Forward(msg *PssMsg) error { + + if self.isSelfRecipient(msg) { + return errorForwardToSelf + } + + digest, err := self.storeMsg(msg) + if err != nil { + log.Warn(fmt.Sprintf("could not store message %v to cache: %v", msg, err)) + } + + if self.checkFwdCache(nil, digest) { + log.Trace(fmt.Sprintf("pss relay block-cache match: FROM %x TO %x", common.ByteLabel(self.Overlay.BaseAddr()), common.ByteLabel(msg.To))) + return nil + } + + // TODO:check integrity of message + sent := 0 + + // send with kademlia + // find the closest peer to the recipient and attempt to send + self.Overlay.EachConn(msg.To, 256, func(op network.OverlayConn, po int, isproxbin bool) bool { + p, ok := op.(senderPeer) + if !ok { + return true + } + addr := self.Overlay.BaseAddr() + sendMsg := fmt.Sprintf("%x: msg to %x via %x", common.ByteLabel(addr), common.ByteLabel(msg.To), common.ByteLabel(p.Address())) + if self.checkFwdCache(p.Address(), digest) { + log.Info(fmt.Sprintf("%v: peer already forwarded to", sendMsg)) + return true + } + err := p.Send(msg) + if err != nil { + log.Warn(fmt.Sprintf("%v: failed forwarding: %v", sendMsg, err)) + return true + } + log.Trace(fmt.Sprintf("%v: successfully forwarded", sendMsg)) + sent++ + // if equality holds, p is always the first peer given in the iterator + if bytes.Equal(msg.To, p.Address()) || !isproxbin { + return false + } + log.Trace(fmt.Sprintf("%x is in proxbin, keep forwarding", common.ByteLabel(p.Address()))) + return true + }) + + if sent == 0 { + log.Error("PSS: unable to forward to any peers") + return nil + } + + self.addFwdCacheExpire(digest) + return nil +} + // Links a pss peer address and topic to a dedicated p2p.MsgReadWriter in the pss peerpool, and runs the specificed protocol on this p2p.MsgReadWriter and the specified peer // // The effect is that now we have a "virtual" protocol running on an artificial p2p.Peer, which can be looked up and piped to through Pss using swarm overlay address and topic @@ -249,121 +420,32 @@ func (self *Pss) AddPeer(p *p2p.Peer, addr pot.Address, run adapters.RunProtocol go func() { err := run(p, rw) log.Warn(fmt.Sprintf("pss vprotocol quit on addr %v topic %v: %v", addr, topic, err)) + self.removePeerTopic(rw, topic) }() return nil } -// Removes a pss peer from the pss peerpool -func (self *Pss) RemovePeer(id pot.Address) { - self.lock.Lock() - defer self.lock.Unlock() - self.peerPool[id] = nil - return -} - func (self *Pss) addPeerTopic(id pot.Address, topic PssTopic, rw p2p.MsgReadWriter) error { - if self.peerPool[id][topic] == nil { + if self.peerPool[id] == nil { self.peerPool[id] = make(map[PssTopic]p2p.MsgReadWriter, PssPeerTopicDefaultCapacity) } self.peerPool[id][topic] = rw return nil } -func (self *Pss) removePeerTopic(id pot.Address, topic PssTopic) { - self.peerPool[id][topic] = nil - return +func (self *Pss) removePeerTopic(rw p2p.MsgReadWriter, topic PssTopic) { + prw, ok := rw.(*PssReadWriter) + if !ok { + return + } + delete(self.peerPool[prw.To], topic) + if len(self.peerPool[prw.To]) == 0 { + delete(self.peerPool, prw.To) + } } func (self *Pss) isActive(id pot.Address, topic PssTopic) bool { - if self.peerPool[id][topic] == nil { - return false - } - return true -} - -func (self *Pss) registerFeed(topic PssTopic) { - self.events[topic] = &event.Feed{} -} - -func (self *Pss) alertSubscribers(topic *PssTopic, msg []byte) error { - feed, ok := self.events[*topic] - if !ok { - return fmt.Errorf("No subscriptions registered for topic %v", topic) - } - numsent := feed.Send(msg) - log.Trace(fmt.Sprintf("pss sent to %d subscribers", numsent)) - return nil -} - -// Sends a message using pss. The message could be anything at all, and will be handled by whichever handler function is mapped to PssTopic using *Pss.Register() -// -// The to address is a swarm overlay address -func (self *Pss) Send(to []byte, topic PssTopic, msg []byte) error { - - pssenv := pssEnvelope{ - SenderOAddr: self.Overlay.GetAddr().OverlayAddr(), - SenderUAddr: self.Overlay.GetAddr().UnderlayAddr(), - Topic: topic, - TTL: DefaultTTL, - Payload: msg, - } - - pssmsg := &PssMsg{ - Payload: pssenv, - } - pssmsg.SetRecipient(to) - - return self.Forward(pssmsg) -} - -// Forwards a pss message to the peer(s) closest to the to address -// -// Handlers that want to pass on a message should call this directly -func (self *Pss) Forward(msg *PssMsg) error { - - if self.IsSelfRecipient(msg) { - return errorForwardToSelf - } - - digest := self.hashMsg(msg) - - if self.checkFwdCache(nil, digest) { - log.Trace(fmt.Sprintf("pss relay block-cache match: FROM %x TO %x", common.ByteLabel(self.Overlay.GetAddr().OverlayAddr()), common.ByteLabel(msg.GetRecipient()))) - //return errorBlockByCache - return nil - } - - // TODO:check integrity of message - - sent := 0 - - // send with kademlia - // find the closest peer to the recipient and attempt to send - self.Overlay.EachLivePeer(msg.GetRecipient(), 256, func(p Peer, po int, isproxbin bool) bool { - if self.checkFwdCache(p.OverlayAddr(), digest) { - log.Warn(fmt.Sprintf("BOUNCE DEFER PSS-relay FROM %x TO %x THRU %x:", common.ByteLabel(self.Overlay.GetAddr().OverlayAddr()), common.ByteLabel(msg.GetRecipient()), common.ByteLabel(p.OverlayAddr()))) - return true - } - log.Warn(fmt.Sprintf("Attempting PSS-relay FROM %x TO %x THRU %x", common.ByteLabel(self.Overlay.GetAddr().OverlayAddr()), common.ByteLabel(msg.GetRecipient()), common.ByteLabel(p.OverlayAddr()))) - err := p.Send(msg) - if err != nil { - log.Warn(fmt.Sprintf("FAILED PSS-relay FROM %x TO %x THRU %x: %v", common.ByteLabel(self.Overlay.GetAddr().OverlayAddr()), common.ByteLabel(msg.GetRecipient()), common.ByteLabel(p.OverlayAddr()), err)) - return true - } - sent++ - if bytes.Equal(msg.GetRecipient(), p.OverlayAddr()) || !isproxbin { - return false - } - log.Trace(fmt.Sprintf("%x is in proxbin, so we continue sending", common.ByteLabel(p.OverlayAddr()))) - return true - }) - if sent == 0 { - return fmt.Errorf("PSS Was not able to send to any peers") - } else { - self.addFwdCacheExpire(digest) - } - - return nil + return self.peerPool[id][topic] != nil } // Convenience object that: @@ -374,36 +456,34 @@ func (self *Pss) Forward(msg *PssMsg) error { // Implements p2p.MsgReadWriter type PssReadWriter struct { *Pss - RecipientOAddr pot.Address - LastActive time.Time - rw chan p2p.Msg - ct *protocols.CodeMap - topic *PssTopic + To pot.Address + LastActive time.Time + rw chan p2p.Msg + spec *protocols.Spec + topic *PssTopic } // Implements p2p.MsgReader func (prw PssReadWriter) ReadMsg() (p2p.Msg, error) { msg := <-prw.rw - log.Trace(fmt.Sprintf("pssrw readmsg: %v", msg)) - return msg, nil } // Implements p2p.MsgWriter func (prw PssReadWriter) WriteMsg(msg p2p.Msg) error { log.Trace(fmt.Sprintf("pssrw writemsg: %v", msg)) - ifc, found := prw.ct.GetInterface(msg.Code) + ifc, found := prw.spec.NewMsg(msg.Code) if !found { return fmt.Errorf("Writemsg couldn't find matching interface for code %d", msg.Code) } msg.Decode(ifc) - to := prw.RecipientOAddr.Bytes() - - pmsg, _ := makeMsg(msg.Code, ifc) - - return prw.Pss.Send(to, *prw.topic, pmsg) + pmsg, err := newProtocolMsg(msg.Code, ifc) + if err != nil { + return err + } + return prw.Pss.Send(prw.To.Bytes(), *prw.topic, pmsg) } // Injects a p2p.Msg into the MsgReadWriter, so that it appears on the associated p2p.MsgReader @@ -416,50 +496,41 @@ func (prw PssReadWriter) injectMsg(msg p2p.Msg) error { // Convenience object for passing messages in and out of the p2p layer type PssProtocol struct { *Pss - virtualProtocol *p2p.Protocol + proto *p2p.Protocol topic *PssTopic - ct *protocols.CodeMap + spec *protocols.Spec } // Constructor -func NewPssProtocol(pss *Pss, topic *PssTopic, ct *protocols.CodeMap, targetprotocol *p2p.Protocol) *PssProtocol { +//func RegisterPssProtocol(pss *Pss, topic *PssTopic, spec *protocols.Spec, targetprotocol *p2p.Protocol) *PssProtocol { +func RegisterPssProtocol(pss *Pss, topic *PssTopic, spec *protocols.Spec, targetprotocol *p2p.Protocol) error { pp := &PssProtocol{ Pss: pss, - virtualProtocol: targetprotocol, + proto: targetprotocol, topic: topic, - ct: ct, + spec: spec, } - return pp -} - -// Retrieves a convenience method for passing an incoming message into the p2p layer -// -// If the implementer wishes to use the p2p.Protocol (or p2p/protocols) message handling, this handler can be directly registered as a handler for the PssMsg structure -func (self *PssProtocol) GetHandler() func([]byte, *p2p.Peer, []byte) error { - return self.handle + pss.Register(topic, pp.handle) + //return pp + return nil } func (self *PssProtocol) handle(msg []byte, p *p2p.Peer, senderAddr []byte) error { hashoaddr := pot.NewHashAddressFromBytes(senderAddr).Address if !self.isActive(hashoaddr, *self.topic) { rw := &PssReadWriter{ - Pss: self.Pss, - RecipientOAddr: hashoaddr, - rw: make(chan p2p.Msg), - ct: self.ct, - topic: self.topic, + Pss: self.Pss, + To: hashoaddr, + rw: make(chan p2p.Msg), + spec: self.spec, + topic: self.topic, } - self.Pss.AddPeer(p, hashoaddr, self.virtualProtocol.Run, *self.topic, rw) + self.Pss.AddPeer(p, hashoaddr, self.proto.Run, *self.topic, rw) } - payload := &pssPayload{} - rlp.DecodeBytes(msg, payload) - - pmsg := p2p.Msg{ - Code: payload.Code, - Size: uint32(len(payload.Data)), - ReceivedAt: time.Now(), - Payload: bytes.NewBuffer(payload.Data), + pmsg, err := ToP2pMsg(msg) + if err != nil { + return fmt.Errorf("could not decode pssmsg") } vrw := self.Pss.peerPool[hashoaddr][*self.topic].(*PssReadWriter) @@ -468,26 +539,11 @@ func (self *PssProtocol) handle(msg []byte, p *p2p.Peer, senderAddr []byte) erro return nil } -func (self *Pss) IsSelfRecipient(msg *PssMsg) bool { - if bytes.Equal(msg.GetRecipient(), self.Overlay.GetAddr().OverlayAddr()) { - return true - } - return false +func (self *Pss) isSelfRecipient(msg *PssMsg) bool { + return bytes.Equal(msg.To, self.Overlay.BaseAddr()) } -func (self *Pss) GetPingHandler() func([]byte, *p2p.Peer, []byte) error { - pingtopic, _ := MakeTopic(pingTopicName, pingTopicVersion) - return func(msg []byte, p *p2p.Peer, from []byte) error { - if bytes.Equal([]byte("ping"), msg) { - log.Trace(fmt.Sprintf("swarm pss ping from %x sending pong", common.ByteLabel(from))) - self.Send(from, pingtopic, []byte("pong")) - } - return nil - } -} - -// Pre-Whisper placeholder -func makeMsg(code uint64, msg interface{}) ([]byte, error) { +func newProtocolMsg(code uint64, msg interface{}) ([]byte, error) { rlpdata, err := rlp.EncodeToBytes(msg) if err != nil { @@ -497,30 +553,39 @@ func makeMsg(code uint64, msg interface{}) ([]byte, error) { // previous attempts corrupted nested structs in the payload iself upon deserializing // therefore we use two separate []byte fields instead of peerAddr // TODO verify that nested structs cannot be used in rlp - smsg := &pssPayload{ + smsg := &PssProtocolMsg{ Code: code, Size: uint32(len(rlpdata)), - Data: rlpdata, + Payload: rlpdata, } - rlpbundle, err := rlp.EncodeToBytes(smsg) - if err != nil { - return nil, err - } - - return rlpbundle, nil + return rlp.EncodeToBytes(smsg) } -// Compiles a new PssTopic from a given name and version. +// constructs a new PssTopic from a given name and version. // // Analogous to the name and version members of p2p.Protocol -func MakeTopic(s string, v int) (PssTopic, error) { - t := [TopicLength]byte{} - if len(s)+4 <= TopicLength { - copy(t[4:len(s)+4], s) - } else { - return t, fmt.Errorf("topic '%t' too long", s) - } - binary.PutVarint(t[:4], int64(v)) - return t, nil +func NewTopic(s string, v int) (topic PssTopic) { + h := sha3.NewKeccak256() + h.Write([]byte(s)) + buf := make([]byte, TopicLength / 8) + binary.PutUvarint(buf, uint64(v)) + h.Write(buf) + copy(topic[:], h.Sum(buf)[:]) + return topic +} + + +func ToP2pMsg(msg []byte) (p2p.Msg, error) { + payload := &PssProtocolMsg{} + if err := rlp.DecodeBytes(msg, payload); err != nil { + return p2p.Msg{}, fmt.Errorf("pss protocol handler unable to decode payload as p2p message: %v", err) + } + + return p2p.Msg{ + Code: payload.Code, + Size: uint32(len(payload.Payload)), + ReceivedAt: time.Now(), + Payload: bytes.NewBuffer(payload.Payload), + }, nil } diff --git a/swarm/network/pss_test.go b/swarm/pss/pss_test.go similarity index 67% rename from swarm/network/pss_test.go rename to swarm/pss/pss_test.go index c0e385c4a7..d9849d042a 100644 --- a/swarm/network/pss_test.go +++ b/swarm/pss/pss_test.go @@ -1,37 +1,417 @@ -package network +package pss import ( + "bytes" "context" "encoding/hex" "fmt" - "math/rand" - "net" - "net/http" + "io/ioutil" "os" - "strconv" "testing" "time" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/protocols" "github.com/ethereum/go-ethereum/p2p/simulations" "github.com/ethereum/go-ethereum/p2p/simulations/adapters" - "github.com/ethereum/go-ethereum/rpc" + p2ptest "github.com/ethereum/go-ethereum/p2p/testing" + "github.com/ethereum/go-ethereum/swarm/network" + "github.com/ethereum/go-ethereum/swarm/storage" ) const ( - protocolName = "foo" - protocolVersion = 42 + pssServiceName = "pss" + bzzServiceName = "bzz" ) +var services = newServices() + func init() { + adapters.RegisterServices(services) h := log.CallerFileHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(true))) log.Root().SetHandler(h) } +func TestPssCache(t *testing.T) { + var err error + to, _ := hex.DecodeString("08090a0b0c0d0e0f1011121314150001020304050607161718191a1b1c1d1e1f") + oaddr, _ := hex.DecodeString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + //uaddr, _ := hex.DecodeString("101112131415161718191a1b1c1d1e1f000102030405060708090a0b0c0d0e0f") + //proofbytes := []byte{241, 172, 117, 105, 88, 154, 82, 33, 176, 188, 91, 244, 245, 85, 86, 16, 120, 232, 70, 45, 182, 188, 99, 103, 157, 3, 202, 121, 252, 21, 129, 22} + proofbytes, _ := hex.DecodeString("ad312dca94df401555cfdeb85a6a1f87fb8f240f08dc36af246bd9d4d41efd89") + ps := newTestPss(oaddr) + pp := NewPssParams() + data := []byte("foo") + datatwo := []byte("bar") + fwdaddr := network.RandomAddr() + msg := &PssMsg{ + Payload: &PssEnvelope{ + TTL: 0, + From: oaddr, + Topic: pssPingTopic, + Payload: data, + }, + To: to, + } + + msgtwo := &PssMsg{ + Payload: &PssEnvelope{ + TTL: 0, + From: oaddr, + Topic: pssPingTopic, + Payload: datatwo, + }, + To: to, + } + + digest, err := ps.storeMsg(msg) + if err != nil { + t.Fatalf("could not store cache msgone: %v", err) + } + digesttwo, err := ps.storeMsg(msgtwo) + if err != nil { + t.Fatalf("could not store cache msgtwo: %v", err) + } + + if !bytes.Equal(digest[:], proofbytes) { + t.Fatalf("digest - got: %x, expected: %x", digest, proofbytes) + } + + if digest == digesttwo { + t.Fatalf("different msgs return same crc: %d", digesttwo) + } + + // check the sender cache + err = ps.addFwdCacheSender(fwdaddr.Over(), digest) + if err != nil { + t.Fatalf("write to pss sender cache failed: %v", err) + } + + if !ps.checkFwdCache(fwdaddr.Over(), digest) { + t.Fatalf("message %v should have SENDER record in cache but checkCache returned false", msg) + } + + if ps.checkFwdCache(fwdaddr.Over(), digesttwo) { + t.Fatalf("message %v should NOT have SENDER record in cache but checkCache returned true", msgtwo) + } + + // check the expire cache + err = ps.addFwdCacheExpire(digest) + if err != nil { + t.Fatalf("write to pss expire cache failed: %v", err) + } + + if !ps.checkFwdCache(nil, digest) { + t.Fatalf("message %v should have EXPIRE record in cache but checkCache returned false", msg) + } + + if ps.checkFwdCache(nil, digesttwo) { + t.Fatalf("message %v should NOT have EXPIRE record in cache but checkCache returned true", msgtwo) + } + + time.Sleep(pp.Cachettl) + if ps.checkFwdCache(nil, digest) { + t.Fatalf("message %v should have expired from cache but checkCache returned true", msg) + } + + err = ps.AddToCache(fwdaddr.Over(), msgtwo) + if err != nil { + t.Fatalf("public accessor cache write failed: %v", err) + } + + if !ps.checkFwdCache(fwdaddr.Over(), digesttwo) { + t.Fatalf("message %v should have SENDER record in cache but checkCache returned false", msgtwo) + } +} + +func TestPssRegisterHandler(t *testing.T) { + var err error + addr := network.RandomAddr() + ps := newTestPss(addr.OAddr) + from := network.RandomAddr() + payload := []byte("payload") + topic := NewTopic(pssSpec.Name, int(pssSpec.Version)) + wrongtopic := NewTopic("foo", 42) + checkMsg := func(msg []byte, p *p2p.Peer, sender []byte) error { + if !bytes.Equal(from.OAddr, sender) { + return fmt.Errorf("sender mismatch. expected %x, got %x", from.OAddr, sender) + } + if !bytes.Equal(msg, payload) { + return fmt.Errorf("sender mismatch. expected %x, got %x", msg, payload) + } + return nil + } + deregister := ps.Register(&topic, checkMsg) + pssmsg := &PssMsg{Payload: NewPssEnvelope(from.OAddr, topic, payload)} + err = ps.Process(pssmsg) + if err != nil { + t.Fatal(err) + } + var i int + err = ps.Process(&PssMsg{Payload: NewPssEnvelope(from.OAddr, wrongtopic, payload)}) + expErr := "" + if err == nil || err.Error() == expErr { + t.Fatalf("unhandled topic expected '%v', got '%v'", expErr, err) + } + deregister2 := ps.Register(&topic, func(msg []byte, p *p2p.Peer, sender []byte) error { i++; return nil }) + err = ps.Process(pssmsg) + if err != nil { + t.Fatal(err) + } + if i != 1 { + t.Fatalf("second registerer handler did not run") + } + deregister() + deregister2() + err = ps.Process(&PssMsg{Payload: NewPssEnvelope(from.OAddr, topic, payload)}) + expErr = "" + if err == nil || err.Error() == expErr { + t.Fatalf("reregister handler expected %v, got %v", expErr, err) + } +} + +func TestPssSimpleLinear(t *testing.T) { + var err error + nodeconfig := adapters.RandomNodeConfig() + addr := network.NewAddrFromNodeId(nodeconfig.Id) + _ = p2ptest.NewTestPeerPool() + ps := newTestPss(addr.Over()) + + ping := &pssPing{ + quitC: make(chan struct{}), + } + + err = RegisterPssProtocol(ps, &pssPingTopic, pssPingProtocol, newPssPingProtocol(ping.pssPingHandler)) + + if err != nil { + t.Fatalf("Failed to register virtual protocol in pss: %v", err) + } + run := func(p *p2p.Peer, rw p2p.MsgReadWriter) error { + id := p.ID() + bp := &testPssPeer{ + Peer: protocols.NewPeer(p, rw, pssSpec), + addr: network.ToOverlayAddr(id[:]), + } + ps.Overlay.On(bp) + defer ps.Overlay.Off(bp) + log.Debug(fmt.Sprintf("%v", ps.Overlay)) + return bp.Run(ps.handlePssMsg) + } + + pt := p2ptest.NewProtocolTester(t, nodeconfig.Id, 2, run) + + msg := newPssPingMsg(ps, network.ToOverlayAddr(pt.Ids[0].Bytes()), pssPingProtocol, pssPingTopic, []byte{1, 2, 3}) + + exchange := p2ptest.Exchange{ + Expects: []p2ptest.Expect{ + p2ptest.Expect{ + Code: 0, + Msg: msg, + Peer: pt.Ids[0], + }, + }, + Triggers: []p2ptest.Trigger{ + p2ptest.Trigger{ + Code: 0, + Msg: msg, + Peer: pt.Ids[1], + }, + }, + } + + err = pt.TestExchanges(exchange) + if err != nil { + t.Fatalf("exchange failed %v", err) + } +} + +func TestPssFullRandom10_5_5(t *testing.T) { + adapter := adapters.NewSimAdapter(services) + testPssFullRandom(t, adapter, 10, 5, 5) +} + +func testPssFullRandom(t *testing.T, adapter adapters.NodeAdapter, nodecount int, fullnodecount int, msgcount int) { + var lastid *adapters.NodeId = nil + + nodeCount := 5 + net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{ + Id: "0", + DefaultService: "psstest", + }) + defer net.Shutdown() + + trigger := make(chan *adapters.NodeId) + ids := make([]*adapters.NodeId, nodeCount) + + for i := 0; i < nodeCount; i++ { + nodeconfig := adapters.RandomNodeConfig() + nodeconfig.Service = "psstest" + node, err := net.NewNodeWithConfig(nodeconfig) + if err != nil { + t.Fatalf("error starting node: %s", err) + } + + if err := net.Start(node.ID()); err != nil { + t.Fatalf("error starting node %s: %s", node.ID().Label(), err) + } + + if err := triggerChecks(trigger, net, node.ID()); err != nil { + t.Fatal("error triggering checks for node %s: %s", node.ID().Label(), err) + } + ids[i] = node.ID() + } + + // run a simulation which connects the 10 nodes in a ring and waits + // for full peer discovery + action := func(ctx context.Context) error { + for i, id := range ids { + var peerId *adapters.NodeId + if i == 0 { + peerId = ids[len(ids)-1] + } else { + peerId = ids[i-1] + } + if err := net.Connect(id, peerId); err != nil { + return err + } + } + return nil + } + check := func(ctx context.Context, id *adapters.NodeId) (bool, error) { + select { + case <-ctx.Done(): + return false, ctx.Err() + default: + } + + node := net.GetNode(id) + if node == nil { + return false, fmt.Errorf("unknown node: %s", id) + } + client, err := node.Client() + if err != nil { + return false, fmt.Errorf("error getting node client: %s", err) + } + + log.Debug("in check", "node", id) + + if lastid != nil { + //msg := pssPingMsg{Created: time.Now(),} + client.CallContext(context.Background(), nil, "pss_sendRaw", pssPingTopic, PssAPIMsg{ + Addr: lastid.Bytes(), + Msg: []byte{1, 2, 3}, + }) + } + lastid = id + + return true, nil + } + + timeout := 5 * time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + result := simulations.NewSimulation(net).Run(ctx, &simulations.Step{ + Action: action, + Trigger: trigger, + Expect: &simulations.Expectation{ + Nodes: ids, + Check: check, + }, + }) + if result.Error != nil { + t.Fatalf("simulation failed: %s", result.Error) + } + + t.Log("Simulation Passed:") + t.Logf("Duration: %s", result.FinishedAt.Sub(result.StartedAt)) + + time.Sleep(time.Second * 2) +} + +// triggerChecks triggers a simulation step check whenever a peer is added or +// removed from the given node +func triggerChecks(trigger chan *adapters.NodeId, net *simulations.Network, id *adapters.NodeId) error { + node := net.GetNode(id) + if node == nil { + return fmt.Errorf("unknown node: %s", id) + } + go func() { + time.Sleep(time.Second) + trigger <- id + }() + /* + client, err := node.Client() + if err != nil { + return err + } + events := make(chan PssAPIMsg) + sub, err := client.Subscribe(context.Background(), "pss", events, "newMsg", topic) + if err != nil { + return fmt.Errorf("error getting peer events for node %v: %s", id, err) + } + go func() { + defer sub.Unsubscribe() + for { + select { + case msg := <-events: + log.Warn("pss rpc got msg", "msg", msg) + trigger <- id + case err := <-sub.Err(): + if err != nil { + log.Error(fmt.Sprintf("error getting peer events for node %v", id), "err", err) + } + return + } + } + }() + */ + return nil +} + +func newServices() adapters.Services { + + return adapters.Services{ + "psstest": func(id *adapters.NodeId, snapshot []byte) []node.Service { + addr := network.NewAddrFromNodeId(id) + + kadparams := network.NewKadParams() + kadparams.MinProxBinSize = 2 + kadparams.MaxBinSize = 3 + kadparams.MinBinSize = 1 + kadparams.MaxRetries = 1000 + kadparams.RetryExponent = 2 + kadparams.RetryInterval = 1000000 + kademlia := network.NewKademlia(addr.Over(), kadparams) + + config := &network.BzzConfig{ + OverlayAddr: addr.Over(), + UnderlayAddr: addr.Under(), + HiveParams: network.NewHiveParams(), + } + + config.HiveParams.KeepAliveInterval = time.Second + + cachedir, err := ioutil.TempDir("", "pss-cache") + if err != nil { + log.Error("create pss cache tmpdir failed", "error", err) + return nil + } + dpa, err := storage.NewLocalDPA(cachedir) + if err != nil { + log.Error("local dpa creation failed", "error", err) + return nil + } + pssp := NewPssParams() + + return []node.Service{network.NewBzz(config, kademlia, adapters.NewSimStateStore()), NewPss(kademlia, dpa, pssp)} + }, + } +} + +/* // example protocol implementation peer // message handlers are methods of this // channels allow receipt reporting from p2p.Protocol message handler @@ -59,27 +439,19 @@ type pssTestNode struct { apifunc func() []rpc.API } -func (n *pssTestNode) Add(peer Peer) error { +func (n *pssTestNode) Add(peer *bzzPeer) error { err := n.Hive.Add(peer) time.Sleep(time.Millisecond * 250) n.triggerCheck() return err } -func (n *pssTestNode) Remove(peer Peer) { - n.Hive.Remove(peer) -} - -func (n *pssTestNode) hiveKeepAlive() <-chan time.Time { - return time.Tick(time.Second * 10) -} - func (n *pssTestNode) triggerCheck() { go func() { n.trigger <- n.id }() } func (n *pssTestNode) OverlayAddr() []byte { - return n.Pss.Overlay.GetAddr().OverlayAddr() + return n.Pss.Overlay.BaseAddr() } func (n *pssTestNode) UnderlayAddr() []byte { @@ -102,8 +474,9 @@ type pssTestService struct { func newPssTestService(t *testing.T, handlefunc func(interface{}) error, testnode *pssTestNode) *pssTestService { hp := NewHiveParams() - //hp.CallInterval = 250 - testnode.Hive = NewHive(hp, testnode.Pss.Overlay) + hp.KeepAliveInterval = 300 + bzz := NewBzz(testnode.OverlayAddr(), testnode.UnderlayAddr(), newTestStore()) + testnode.Hive = NewHive(hp, testnode.Pss.Overlay, bzz) return &pssTestService{ //nid := adapters.NewNodeId(addr.UnderlayAddr()) msgFunc: handlefunc, @@ -111,8 +484,8 @@ func newPssTestService(t *testing.T, handlefunc func(interface{}) error, testnod } } -func (self *pssTestService) Start(server p2p.Server) error { - return self.node.Hive.Start(server, self.node.hiveKeepAlive) +func (self *pssTestService) Start(server *p2p.Server) error { + return self.node.Hive.Start(server) } func (self *pssTestService) Stop() error { @@ -121,24 +494,13 @@ func (self *pssTestService) Stop() error { } func (self *pssTestService) Protocols() []p2p.Protocol { - ct := BzzCodeMap() - for _, m := range DiscoveryMsgs { - ct.Register(m) - } - ct.Register(&PssMsg{}) - - srv := func(p Peer) error { - p.Register(&PssMsg{}, self.msgFunc) - self.node.Add(p) - p.DisconnectHook(func(err error) { - self.node.Remove(p) - }) - return nil - } - - proto := Bzz(self.node.OverlayAddr(), self.node.UnderlayAddr(), ct, srv, nil, nil) - - return []p2p.Protocol{*proto} + bzz := NewBzz(self.node.OverlayAddr(), self.node.UnderlayAddr(), newTestStore()) + return append(self.node.Hive.Protocols(), p2p.Protocol{ + Name: PssProtocolName, + Version: PssProtocolVersion, + Length: PssProtocol.Length(), + Run: bzz.RunProtocol(PssProtocol, self.Run), + }) } func (self *pssTestService) APIs() []rpc.API { @@ -153,142 +515,15 @@ func (self *pssTestService) APIs() []rpc.API { return nil } -func TestPssCache(t *testing.T) { - var err error - to, _ := hex.DecodeString("08090a0b0c0d0e0f1011121314150001020304050607161718191a1b1c1d1e1f") - oaddr, _ := hex.DecodeString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") - uaddr, _ := hex.DecodeString("101112131415161718191a1b1c1d1e1f000102030405060708090a0b0c0d0e0f") - ps := makePss(oaddr) - pp := NewPssParams() - topic, _ := MakeTopic(protocolName, protocolVersion) - data := []byte("foo") - fwdaddr := RandomAddr() - msg := &PssMsg{ - Payload: pssEnvelope{ - TTL: 0, - SenderOAddr: oaddr, - SenderUAddr: uaddr, - Topic: topic, - Payload: data, - }, - } - msg.SetRecipient(to) - - msgtwo := &PssMsg{ - Payload: pssEnvelope{ - TTL: 0, - SenderOAddr: uaddr, - SenderUAddr: oaddr, - Topic: topic, - Payload: data, - }, - } - msgtwo.SetRecipient(to) - - digest := ps.hashMsg(msg) - digesttwo := ps.hashMsg(msgtwo) - - if digest != 3595343914 { - t.Fatalf("digest - got: %d, expected: %d", digest, 3595343914) - } - - if digest == digesttwo { - t.Fatalf("different msgs return same crc: %d", digesttwo) - } - - // check the sender cache - err = ps.addFwdCacheSender(fwdaddr.OverlayAddr(), digest) - if err != nil { - t.Fatalf("write to pss sender cache failed: %v", err) - } - - if !ps.checkFwdCache(fwdaddr.OverlayAddr(), digest) { - t.Fatalf("message %v should have SENDER record in cache but checkCache returned false", msg) - } - - if ps.checkFwdCache(fwdaddr.OverlayAddr(), digesttwo) { - t.Fatalf("message %v should NOT have SENDER record in cache but checkCache returned true", msgtwo) - } - - // check the expire cache - err = ps.addFwdCacheExpire(digest) - if err != nil { - t.Fatalf("write to pss expire cache failed: %v", err) - } - - if !ps.checkFwdCache(nil, digest) { - t.Fatalf("message %v should have EXPIRE record in cache but checkCache returned false", msg) - } - - if ps.checkFwdCache(nil, digesttwo) { - t.Fatalf("message %v should NOT have EXPIRE record in cache but checkCache returned true", msgtwo) - } - - time.Sleep(pp.Cachettl) - if ps.checkFwdCache(nil, digest) { - t.Fatalf("message %v should have expired from cache but checkCache returned true", msg) - } - - err = ps.AddToCache(fwdaddr.OverlayAddr(), msgtwo) - if err != nil { - t.Fatalf("public accessor cache write failed: %v", err) - } - - if !ps.checkFwdCache(fwdaddr.OverlayAddr(), digesttwo) { - t.Fatalf("message %v should have SENDER record in cache but checkCache returned false", msgtwo) - } +func (self *pssTestService) Run(peer *bzzPeer) error { + self.node.Add(peer) + defer self.node.Remove(peer) + return peer.Run(self.msgFunc) } +*/ -func TestPssRegisterHandler(t *testing.T) { - var topic PssTopic - var err error - addr := RandomAddr() - ps := makePss(addr.UnderlayAddr()) +/* - topic, _ = MakeTopic(protocolName, protocolVersion) - err = ps.Register(topic, func(msg []byte, p *p2p.Peer, sender []byte) error { return nil }) - if err != nil { - t.Fatalf("couldnt register protocol 'foo' v 42: %v", err) - } - - topic, _ = MakeTopic(protocolName, protocolVersion) - err = ps.Register(topic, func(msg []byte, p *p2p.Peer, sender []byte) error { return nil }) - if err == nil { - t.Fatalf("register protocol 'abc..789' v 65536 should have failed") - } -} - -func TestPssFullRandom10_10_5(t *testing.T) { - testPssFullRandom(t, 10, 10, 5) -} - -func TestPssFullRandom50_50_5(t *testing.T) { - testPssFullRandom(t, 50, 50, 5) -} - -func TestPssFullRandom50_50_25(t *testing.T) { - testPssFullRandom(t, 50, 50, 25) -} - -func TestPssFullRandom10_100_50(t *testing.T) { - testPssFullRandom(t, 10, 100, 50) -} - -func TestPssFullRandom50_100_50(t *testing.T) { - testPssFullRandom(t, 50, 100, 50) -} - -func TestPssFullRandom100_100_5(t *testing.T) { - testPssFullRandom(t, 100, 100, 5) -} - -func TestPssFullRandom100_100_25(t *testing.T) { - testPssFullRandom(t, 100, 100, 25) -} - -func TestPssFullRandom100_100_50(t *testing.T) { - testPssFullRandom(t, 100, 100, 50) -} func testPssFullRandom(t *testing.T, numsends int, numnodes int, numfullnodes int) { var action func(ctx context.Context) error @@ -305,7 +540,9 @@ func testPssFullRandom(t *testing.T, numsends int, numnodes int, numfullnodes in expectnodesids := []*adapters.NodeId{} // the nodes to expect on (needed by checker) expectnodesresults := make(map[*adapters.NodeId][]int) // which messages expect actually got - vct := protocols.NewCodeMap(protocolName, protocolVersion, 65535, &pssTestPayload{}) + vct := protocols.NewCodeMap(map[uint64]interface{}{ + 0: pssTestPayload{}, + }) topic, _ := MakeTopic(protocolName, protocolVersion) trigger := make(chan *adapters.NodeId) @@ -471,8 +708,8 @@ func testPssFullRandom(t *testing.T, numsends int, numnodes int, numfullnodes in for i := 0; i < len(sends); i += 2 { t.Logf("Pss #%d: oaddr %x -> %x (uaddr %x -> %x)", i/2+1, - common.ByteLabel(nodes[fullnodes[sends[i]]].Pss.GetAddr().OverlayAddr()), - common.ByteLabel(nodes[fullnodes[sends[i+1]]].Pss.GetAddr().OverlayAddr()), + common.ByteLabel(nodes[fullnodes[sends[i]]].Pss.BaseAddr()), + common.ByteLabel(nodes[fullnodes[sends[i+1]]].Pss.BaseAddr()), common.ByteLabel(fullnodes[sends[i]].Bytes()), common.ByteLabel(fullnodes[sends[i+1]].Bytes())) } @@ -484,15 +721,15 @@ func testPssFullRandom(t *testing.T, numsends int, numnodes int, numfullnodes in fails++ } } - t.Logf("Node oaddr %x (uaddr %x) was sent %d msgs, of which %d failed", common.ByteLabel(nodes[id].Pss.GetAddr().OverlayAddr()), common.ByteLabel(id.Bytes()), len(results), fails) + t.Logf("Node oaddr %x (uaddr %x) was sent %d msgs, of which %d failed", common.ByteLabel(nodes[id].Pss.BaseAddr()), common.ByteLabel(id.Bytes()), len(results), fails) totalfails += fails } t.Logf("Total sent: %d, total fail: %d (%.2f%%)", len(sends)/2, totalfails, (float32(totalfails)/float32(len(sends)/2+1))*100) for _, node := range nodes { - logstring := fmt.Sprintf("Node oaddr %x kademlia: ", common.ByteLabel(node.Pss.Overlay.GetAddr().OverlayAddr())) - node.Pss.Overlay.EachLivePeer(nil, 256, func(p Peer, po int, isprox bool) bool { - logstring += fmt.Sprintf("%x ", common.ByteLabel(p.OverlayAddr())) + logstring := fmt.Sprintf("Node oaddr %x kademlia: ", common.ByteLabel(node.Pss.Overlay.BaseAddr())) + node.Pss.Overlay.EachConn(nil, 256, func(p Peer, po int, isprox bool) bool { + logstring += fmt.Sprintf("%x ", common.ByteLabel(p.Over())) return true }) t.Log(logstring) @@ -511,7 +748,8 @@ func TestPssFullLinearEcho(t *testing.T) { var firstpssnode *adapters.NodeId var secondpssnode *adapters.NodeId - vct := protocols.NewCodeMap(protocolName, protocolVersion, 65535, &pssTestPayload{}) + vct := protocols.NewCodeMap(protocolName, protocolVersion, ProtocolMaxMsgSize) + vct.Register(0, &pssTestPayload{}) topic, _ := MakeTopic(protocolName, protocolVersion) fullnodes := []*adapters.NodeId{} @@ -538,15 +776,15 @@ func TestPssFullLinearEcho(t *testing.T) { return err } - /*for i, id := range ids { - var peerId *adapters.NodeId - if i != 0 { - peerId = ids[i-1] - if err := net.Connect(id, peerId); err != nil { - return err - } - } - }*/ + // for i, id := range ids { + // var peerId *adapters.NodeId + // if i != 0 { + // peerId = ids[i-1] + // if err := net.Connect(id, peerId); err != nil { + // return err + // } + // } + // } return nil } check = func(ctx context.Context, id *adapters.NodeId) (bool, error) { @@ -559,9 +797,8 @@ func TestPssFullLinearEcho(t *testing.T) { node, ok := nodes[id] if !ok { return false, fmt.Errorf("unknown node: %s (%v)", id, node) - } else { - log.Trace(fmt.Sprintf("sim check ok node %v", id)) } + log.Trace(fmt.Sprintf("sim check ok node %v", id)) return true, nil } @@ -589,7 +826,7 @@ func TestPssFullLinearEcho(t *testing.T) { // first find a node that we're connected to for firstpssnode == nonode { log.Debug(fmt.Sprintf("Waiting for pss relaypeer for %x close to %x ...", common.ByteLabel(nodes[fullnodes[0]].OverlayAddr()), common.ByteLabel(nodes[ids[1]].OverlayAddr()))) - nodes[fullnodes[0]].Pss.Overlay.EachLivePeer(nodes[fullnodes[1]].OverlayAddr(), 256, func(p Peer, po int, isprox bool) bool { + nodes[fullnodes[0]].Pss.Overlay.EachConn(nodes[fullnodes[1]].OverlayAddr(), 256, func(p Peer, po int, isprox bool) bool { for _, id := range ids { if id.NodeID == p.ID() { firstpssnode = id @@ -609,7 +846,7 @@ func TestPssFullLinearEcho(t *testing.T) { // then find the node it's connected to for secondpssnode == nonode { log.Debug(fmt.Sprintf("PSS kademlia: Waiting for recipientpeer for %x close to %x ...", common.ByteLabel(nodes[firstpssnode].OverlayAddr()), common.ByteLabel(nodes[fullnodes[1]].OverlayAddr()))) - nodes[firstpssnode].Pss.Overlay.EachLivePeer(nodes[fullnodes[1]].OverlayAddr(), 256, func(p Peer, po int, isprox bool) bool { + nodes[firstpssnode].Pss.Overlay.Eachc(nodes[fullnodes[1]].OverlayAddr(), 256, func(p Peer, po int, isprox bool) bool { for _, id := range ids { if id.NodeID == p.ID() && id.NodeID != fullnodes[0].NodeID { secondpssnode = id @@ -692,7 +929,8 @@ func TestPssFullWS(t *testing.T) { var firstpssnode, secondpssnode *adapters.NodeId fullnodes := []*adapters.NodeId{} - vct := protocols.NewCodeMap(protocolName, protocolVersion, 65535, &pssTestPayload{}) + vct := protocols.NewCodeMap(protocolName, protocolVersion, ProtocolMaxMsgSize) + vct.Register(0, &pssTestPayload{}) topic, _ := MakeTopic(pingTopicName, pingTopicVersion) trigger := make(chan *adapters.NodeId) @@ -788,7 +1026,7 @@ func TestPssFullWS(t *testing.T) { // then find the node it's connected to for secondpssnode == nonode { log.Debug(fmt.Sprintf("PSS kademlia: Waiting for recipientpeer for %x close to %x ...", common.ByteLabel(nodes[firstpssnode].OverlayAddr()), common.ByteLabel(nodes[fullnodes[1]].OverlayAddr()))) - nodes[firstpssnode].Pss.Overlay.EachLivePeer(nodes[fullnodes[1]].OverlayAddr(), 256, func(p Peer, po int, isprox bool) bool { + nodes[firstpssnode].Pss.Overlay.EachConn(nodes[fullnodes[1]].OverlayAddr(), 256, func(p Peer, po int, isprox bool) bool { for _, id := range ids { if id.NodeID == p.ID() && id.NodeID != fullnodes[0].NodeID { secondpssnode = id @@ -855,7 +1093,7 @@ func TestPssFullWS(t *testing.T) { action = func(ctx context.Context) error { go func() { clientrecv.EthSubscribe(ctx, ch, "newMsg", topic) - clientsend.Call(nil, "eth_sendRaw", nodes[secondpssnode].Pss.Overlay.GetAddr().OverlayAddr(), topic, []byte("ping")) + clientsend.Call(nil, "eth_sendRaw", nodes[secondpssnode].Pss.Overlay.BaseAddr(), topic, []byte("ping")) trigger <- secondpssnode }() return nil @@ -935,7 +1173,7 @@ func newPssSimulationTester(t *testing.T, numnodes int, numfullnodes int, trigge if testpeers[id] != nil { handlefunc = makePssHandleProtocol(psss[id]) - log.Trace(fmt.Sprintf("Making full protocol id %x addr %x (testpeers %p)", common.ByteLabel(id.Bytes()), common.ByteLabel(addr.OverlayAddr()), testpeers)) + log.Trace(fmt.Sprintf("Making full protocol id %x addr %x (testpeers %p)", common.ByteLabel(id.Bytes()), common.ByteLabel(addr.Over()), testpeers)) } else { handlefunc = makePssHandleForward(psss[id]) } @@ -965,7 +1203,7 @@ func newPssSimulationTester(t *testing.T, numnodes int, numfullnodes int, trigge } for i, conf := range configs { addr := NewPeerAddrFromNodeId(conf.Id) - psss[conf.Id] = makePss(addr.OverlayAddr()) + psss[conf.Id] = makePss(addr.Over()) if i < numfullnodes { tp := &pssTestPeer{ Peer: &protocols.Peer{ @@ -992,13 +1230,73 @@ func newPssSimulationTester(t *testing.T, numnodes int, numfullnodes int, trigge } func makePss(addr []byte) *Pss { - kp := NewKadParams() + + // set up storage + cachedir, err := ioutil.TempDir("", "pss-cache") + if err != nil { + log.Error("create pss cache tmpdir failed", "error", err) + os.Exit(1) + } + + dpa, err := storage.NewLocalDPA(cachedir) + if err != nil { + log.Error("local dpa creation failed", "error", err) + os.Exit(1) + } + // cannot use pyramidchunker as it still lacks joinfunc TestPssRegisterHandler(t *testing.T) { + addr := RandomAddr() + ps := newTestPss(addr.UnderlayAddr()) + from := RandomAddr() + payload := []byte("payload") + topic := NewTopic(protocolName, protocolVersion) + checkMsg := func(msg []byte, p *p2p.Peer, sender []byte) error { + if !bytes.Equal(from.OverlayAddr(), sender) { + return fmt.Errorf("sender mismatch. expected %x, got %x", from.OverlayAddr(), sender) + } + if !bytes.Equal(msg, payload) { + return fmt.Errorf("sender mismatch. expected %x, got %x", msg, payload) + } + if !bytes.Equal(from.UnderlayAddr(), p.ID()) { + return fmt.Errorf("sender mismatch. expected %x, got %x", from.UnderlayAddr(), p.ID()) + } + } + deregister := ps.Register(topic, checkMsg) + pssmsg := &PssMsg{Data: NewPssEnvelope(from, topic, payload)} + err = ps.Process(pssmsg) + if err != nil { + t.Fatal(err) + } + var i int + err = ps.Process(&PssMsg{Data: NewPssEnvelope(from, []byte("topic"), payload)}) + expErr := "" + if err == nil || err.Error() != expErr { + t.Fatalf("unhandled topic expected %v, got %v", expErr, err) + } + deregister2 := ps.Register(topic, func(msg []byte, p *p2p.Peer, sender []byte) error { i++; return nil }) + ps.Process(pssmsg) + if err != nil { + t.Fatal(err) + } + if i != 1 { + t.Fatalf("second registerer handler did not run") + } + deregister() + deregister2() + err = ps.Process(&PssMsg{Data: NewPssEnvelope(from, topic, payload)}) + expErr = "" + if err == nil || err.Error() != expErr { + t.Fatalf("reregister handler expected %v, got %v", expErr, err) + } +} + // dpa.Chunker = storage.NewPyramidChunker(storage.NewChunkerParams()) + + kp := network.NewKadParams() kp.MinProxBinSize = 3 pp := NewPssParams() - overlay := NewKademlia(addr, kp) - ps := NewPss(overlay, pp) + overlay := network.NewKademlia(addr, kp) + ps := NewPss(overlay, dpa, pp) //overlay.Prune(time.Tick(time.Millisecond * 250)) return ps } @@ -1018,35 +1316,6 @@ func makeCustomProtocol(name string, version int, ct *protocols.CodeMap, testpee return protocols.NewProtocol(name, uint(version), run, ct, nil, nil) } -func makeFakeMsg(ps *Pss, ct *protocols.CodeMap, topic PssTopic, senderaddr PeerAddr, content string) PssMsg { - data := pssTestPayload{} - code, found := ct.GetCode(&data) - if !found { - return PssMsg{} - } - - data.Data = content - - rlpbundle, err := makeMsg(code, data) - if err != nil { - return PssMsg{} - } - - pssenv := pssEnvelope{ - SenderOAddr: senderaddr.OverlayAddr(), - SenderUAddr: senderaddr.UnderlayAddr(), - Topic: topic, - TTL: DefaultTTL, - Payload: rlpbundle, - } - pssmsg := PssMsg{ - Payload: pssenv, - } - pssmsg.SetRecipient(ps.Overlay.GetAddr().OverlayAddr()) - - return pssmsg -} - func makePssHandleForward(ps *Pss) func(msg interface{}) error { // for the simple check it passes on the message if it's not for us return func(msg interface{}) error { @@ -1110,3 +1379,4 @@ func (ptp *pssTestPeer) SimpleHandlePssPayload(msg interface{}) error { return nil } +*/ diff --git a/swarm/pss/pssapi.go b/swarm/pss/pssapi.go new file mode 100644 index 0000000000..d0d9abd25a --- /dev/null +++ b/swarm/pss/pssapi.go @@ -0,0 +1,76 @@ +package pss + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rpc" +) + +// PssAPI is the RPC API module for Pss +type PssAPI struct { + *Pss +} + +// NewPssAPI constructs a PssAPI instance +func NewPssAPI(ps *Pss) *PssAPI { + return &PssAPI{Pss: ps} +} + +// PssAPIMsg is the type for messages, it extends the rlp encoded protocol Msg +// with the Sender's overlay address +type PssAPIMsg struct { + Msg []byte + Addr []byte +} + +// NewMsg API endpoint creates an RPC subscription +func (pssapi *PssAPI) NewMsg(ctx context.Context, topic PssTopic) (*rpc.Subscription, error) { + notifier, supported := rpc.NotifierFromContext(ctx) + if !supported { + return nil, fmt.Errorf("Subscribe not supported") + } + + psssub := notifier.CreateSubscription() + handler := func(msg []byte, p *p2p.Peer, from []byte) error { + apimsg := &PssAPIMsg{ + Msg: msg, + Addr: from, + } + if err := notifier.Notify(psssub.ID, apimsg); err != nil { + log.Warn(fmt.Sprintf("notification on pss sub topic %v rpc (sub %v) msg %v failed!", topic, psssub.ID, msg)) + } + return nil + } + deregf := pssapi.Pss.Register(&topic, handler) + + go func() { + defer deregf() + //defer psssub.Unsubscribe() + select { + case err := <-psssub.Err(): + log.Warn(fmt.Sprintf("caught subscription error in pss sub topic: %v", topic, err)) + case <-notifier.Closed(): + log.Warn(fmt.Sprintf("rpc sub notifier closed")) + } + }() + + return psssub, nil +} + +// SendRaw sends the message (serialised into byte slice) to a peer with topic +func (pssapi *PssAPI) SendRaw(topic PssTopic, msg PssAPIMsg) error { + err := pssapi.Pss.Send(msg.Addr, topic, msg.Msg) + if err != nil { + return fmt.Errorf("send error: %v", err) + } + return fmt.Errorf("ok sent") +} + +// BaseAddr gets our own overlayaddress +func (pssapi *PssAPI) BaseAddr() ([]byte, error) { + log.Warn("inside baseaddr") + return pssapi.Pss.Overlay.BaseAddr(), nil +} diff --git a/swarm/swarm.go b/swarm/swarm.go index c900f23b61..6676a4e944 100644 --- a/swarm/swarm.go +++ b/swarm/swarm.go @@ -32,13 +32,13 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/simulations/adapters" "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/swarm/api" httpapi "github.com/ethereum/go-ethereum/swarm/api/http" "github.com/ethereum/go-ethereum/swarm/fuse" "github.com/ethereum/go-ethereum/swarm/network" "github.com/ethereum/go-ethereum/swarm/storage" + "github.com/ethereum/go-ethereum/swarm/pss" ) // the swarm stack @@ -162,7 +162,7 @@ Start is called when the stack is started * TODO: start subservices like sword, swear, swarmdns */ // implements the node.Service interface -func (self *Swarm) Start(net p2p.Server) error { +func (self *Swarm) Start(net *p2p.Server) error { // set chequebook if self.swapEnabled { ctx := context.Background() // The initial setup has no deadline. @@ -182,9 +182,10 @@ func (self *Swarm) Start(net p2p.Server) error { func() <-chan time.Time { return time.NewTicker(time.Second).C }, + nil, ) - log.Info(fmt.Sprintf("Swarm network started on bzz address: %v", self.hive.GetAddr())) + log.Info(fmt.Sprintf("Swarm network started on bzz address: %v", self.hive.BaseAddr())) if self.pssEnabled { pssparams := network.NewPssParams() @@ -238,48 +239,46 @@ func (self *Swarm) Stop() error { // implements the node.Service interface func (self *Swarm) Protocols() []p2p.Protocol { ct := network.BzzCodeMap() - for _, m := range network.DiscoveryMsgs { - ct.Register(m) - } if self.pssEnabled { - ct.Register(&network.PssMsg{}) + ct.Register(1, &network.PssMsg{}) } + ct.Register(2, network.DiscoveryMsgs...) - srv := func(p network.Peer) error { - if self.pssEnabled { - p.Register(&network.PssMsg{}, func(msg interface{}) error { - pssmsg := msg.(*network.PssMsg) - - if self.pss.IsSelfRecipient(pssmsg) { - log.Trace("pss for us, yay! ... let's process!") - env := pssmsg.Payload - umsg := env.Payload - f := self.pss.GetHandler(env.Topic) - if f == nil { - return fmt.Errorf("No registered handler for topic '%s'", env.Topic) - } - nid := adapters.NewNodeId(env.SenderUAddr) - p := p2p.NewPeer(nid.NodeID, fmt.Sprintf("%x", common.ByteLabel(nid.Bytes())), []p2p.Cap{}) - return f(umsg, p, env.SenderOAddr) - } else { - log.Trace("pss was for someone else :'( ... forwarding") - return self.pss.Forward(pssmsg) - } - return nil - }) - } - self.hive.Add(p) - p.DisconnectHook(func(err error) { - self.hive.Remove(p) - }) - return nil - } + // srv := func(p network.Peer) error { + // if self.pssEnabled { + // p.Register(&network.PssMsg{}, func(msg interface{}) error { + // pssmsg := msg.(*network.PssMsg) + // + // if self.pss.IsSelfRecipient(pssmsg) { + // log.Trace("pss for us, yay! ... let's process!") + // env := pssmsg.Payload + // umsg := env.Payload + // f := self.pss.GetHandler(env.Topic) + // if f == nil { + // return fmt.Errorf("No registered handler for topic '%s'", env.Topic) + // } + // nid := adapters.NewNodeId(env.SenderUAddr) + // p := p2p.NewPeer(nid.NodeID, fmt.Sprintf("%x", common.ByteLabel(nid.Bytes())), []p2p.Cap{}) + // return f(umsg, p, env.SenderOAddr) + // } else { + // log.Trace("pss was for someone else :'( ... forwarding") + // return self.pss.Forward(pssmsg) + // } + // return nil + // }) + // } + // self.hive.Add(p) + // p.DisconnectHook(func(err error) { + // self.hive.Remove(p) + // }) + // return nil + // } proto := network.Bzz( - self.hive.Overlay.GetAddr().OverlayAddr(), - self.hive.Overlay.GetAddr().UnderlayAddr(), + self.hive.Overlay.GetAddr().Over(), + self.hive.Overlay.GetAddr().Under(), ct, - srv, + nil, nil, nil, ) diff --git a/whisper/whisperv2/main.go b/whisper/whisperv2/main.go index 081e4fe438..a2e49659bc 100644 --- a/whisper/whisperv2/main.go +++ b/whisper/whisperv2/main.go @@ -48,14 +48,16 @@ func main() { shh := whisper.New() // Create an Ethereum peer to communicate through - server := p2p.NewServer(p2p.Config{ - PrivateKey: key, - MaxPeers: 10, - Name: name, - Protocols: []p2p.Protocol{shh.Protocol()}, - ListenAddr: ":30300", - NAT: nat.Any(), - }) + server := &p2p.Server{ + Config: p2p.Config{ + PrivateKey: key, + MaxPeers: 10, + Name: name, + Protocols: []p2p.Protocol{shh.Protocol()}, + ListenAddr: ":30300", + NAT: nat.Any(), + }, + } fmt.Println("Starting Ethereum peer...") if err := server.Start(); err != nil { fmt.Printf("Failed to start Ethereum peer: %v.\n", err) diff --git a/whisper/whisperv2/whisper.go b/whisper/whisperv2/whisper.go index 6d460bdee5..1d7c21bd12 100644 --- a/whisper/whisperv2/whisper.go +++ b/whisper/whisperv2/whisper.go @@ -172,7 +172,7 @@ func (self *Whisper) Send(envelope *Envelope) error { // Start implements node.Service, starting the background data propagation thread // of the Whisper protocol. -func (self *Whisper) Start(p2p.Server) error { +func (self *Whisper) Start(*p2p.Server) error { log.Info(fmt.Sprint("Whisper started")) go self.update() return nil diff --git a/whisper/whisperv5/peer_test.go b/whisper/whisperv5/peer_test.go index 9e3b66c63d..d3cd63b0b2 100644 --- a/whisper/whisperv5/peer_test.go +++ b/whisper/whisperv5/peer_test.go @@ -78,7 +78,7 @@ type TestData struct { type TestNode struct { shh *Whisper id *ecdsa.PrivateKey - server p2p.Server + server *p2p.Server filerId string } @@ -140,17 +140,19 @@ func initialize(t *testing.T) { peers = append(peers, peer) } - node.server = p2p.NewServer(p2p.Config{ - PrivateKey: node.id, - MaxPeers: NumNodes/2 + 1, - Name: name, - Protocols: node.shh.Protocols(), - ListenAddr: addr, - NAT: nat.Any(), - BootstrapNodes: peers, - StaticNodes: peers, - TrustedNodes: peers, - }) + node.server = &p2p.Server{ + Config: p2p.Config{ + PrivateKey: node.id, + MaxPeers: NumNodes/2 + 1, + Name: name, + Protocols: node.shh.Protocols(), + ListenAddr: addr, + NAT: nat.Any(), + BootstrapNodes: peers, + StaticNodes: peers, + TrustedNodes: peers, + }, + } err = node.server.Start() if err != nil { diff --git a/whisper/whisperv5/whisper.go b/whisper/whisperv5/whisper.go index 77ca20a58c..f2aad08efb 100644 --- a/whisper/whisperv5/whisper.go +++ b/whisper/whisperv5/whisper.go @@ -396,7 +396,7 @@ func (w *Whisper) Send(envelope *Envelope) error { // Start implements node.Service, starting the background data propagation thread // of the Whisper protocol. -func (w *Whisper) Start(p2p.Server) error { +func (w *Whisper) Start(*p2p.Server) error { log.Info("started whisper v." + ProtocolVersionStr) go w.update()