From 688b20e9de44a04362de2d6ac5905d4217c7ee73 Mon Sep 17 00:00:00 2001 From: Bruno Bachmann Date: Sat, 13 May 2017 14:19:13 -0700 Subject: [PATCH 01/37] Revert to older greeting message --- main.go | 2 +- peer/peer.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 3017d51..cabbb40 100644 --- a/main.go +++ b/main.go @@ -47,7 +47,7 @@ func main() { } // Send a message to the peer - _, err = stream.Write([]byte("Hello, world!")) + _, err = stream.Write([]byte("Hello, world!\n")) if err != nil { log.Fatal(err) } diff --git a/peer/peer.go b/peer/peer.go index 11464c7..bd981d3 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -94,6 +94,7 @@ func New(ip string, port int) (*Peer, error) { // We may want to implement another type of StreamHandler in the future. func (p *Peer) Receive(s net.Stream) { log.Debug("Setting basic stream handler.") + defer s.Close() p.doCumulus(s) } From f77eb2d5c54139ce781f146790ddb5459ea04efc Mon Sep 17 00:00:00 2001 From: Bruno Bachmann Date: Tue, 16 May 2017 22:48:38 -0700 Subject: [PATCH 02/37] Add preliminary Message and Subnet implementations --- message/message.go | 46 +++++++++++++++++++++++++++++ peer/peer.go | 42 ++++++++++++++++++++++----- peer/peer_test.go | 4 +-- subnet/subnet.go | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 155 insertions(+), 9 deletions(-) create mode 100644 message/message.go create mode 100644 subnet/subnet.go diff --git a/message/message.go b/message/message.go new file mode 100644 index 0000000..66e8ef0 --- /dev/null +++ b/message/message.go @@ -0,0 +1,46 @@ +package message + +import "errors" + +// Message types +// NOTE: because of the way iota works, changing the order in which the +// following constants appear will change their values, which may affect the +// ability of your peer to communicate with others. +const ( + // Send the multiaddress of a peer to another peer + PeerInfo = iota + // Send information about a block that was just hashed + NewBlock = iota + // Request chunk of the blockchain from peer + RequestChunk = iota + // Advertise that we have a chunk of the blockchain + AdvertiseChunk = iota + // Send information about a new transaction to another peer + Transaction = iota +) + +// Message is a container for information and its type that is +// sent between Cumulus peers. +type Message struct { + msgType int + content []byte +} + +// New returns a pointer to a message initialized with a byte array +// of content and a message type, or an error if the type is not one +// of those defined above. +func New(c []byte, t int) (*Message, error) { + switch t { + case PeerInfo: + case NewBlock: + case RequestChunk: + case AdvertiseChunk: + case Transaction: + break + default: + return nil, errors.New("Invalid message type") + } + + m := &Message{msgType: t, content: c} + return m, nil +} diff --git a/peer/peer.go b/peer/peer.go index bd981d3..b2a6338 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -14,16 +14,15 @@ import ( bhost "github.com/libp2p/go-libp2p/p2p/host/basic" ma "github.com/multiformats/go-multiaddr" log "github.com/sirupsen/logrus" + sn "github.com/ubclaunchpad/cumulus/subnet" ) const ( // DefaultPort is the TCP port hosts will communicate over if none is // provided DefaultPort = 8765 - // CumulusProtocol is the name of the protocol peers communicate over CumulusProtocol = "/cumulus/0.0.1" - // DefaultIP is the IP address new hosts will use if none if provided DefaultIP = "127.0.0.1" ) @@ -31,6 +30,7 @@ const ( // Peer is a cumulus Peer composed of a host type Peer struct { host.Host + subnet sn.Subnet } // New creates a Cumulus host with the given IP addr and TCP port. @@ -69,9 +69,11 @@ func New(ip string, port int) (*Peer, error) { return nil, err } + subnet := *sn.New(sn.DefaultMaxPeers) + // Actually create the host and peer with the network we just set up. host := bhost.New(netwrk) - peer := &Peer{Host: host} + peer := &Peer{Host: host, subnet: subnet} // Build host multiaddress hostAddr, _ := ma.NewMultiaddr(fmt.Sprintf("/ipfs/%s", @@ -89,12 +91,22 @@ func New(ip string, port int) (*Peer, error) { } // Receive is the function that gets called when a remote peer -// opens a new stream with the host that SetStreamHandler() is called on. +// opens a new stream with this peer. // This should be passed as the second argument to SetStreamHandler(). // We may want to implement another type of StreamHandler in the future. func (p *Peer) Receive(s net.Stream) { log.Debug("Setting basic stream handler.") defer s.Close() + + // Add the remote peer to this peer's subnet + // TODO: discuss this behaviour (for now refuse connections if subnet full) + remoteMA := s.Conn().RemoteMultiaddr() + err := p.subnet.AddPeer(remoteMA, s) + if err != nil { + log.Warnln(err) + } + defer p.subnet.RemovePeer(remoteMA) + p.doCumulus(s) } @@ -121,7 +133,7 @@ func (p *Peer) doCumulus(s net.Stream) { // given multiaddress. // Returns peer ID (esentially 46 character hash created by the peer) // and the peer's multiaddress in the form /ip4//tcp/. -func ExtractPeerInfo(peerma string) (lpeer.ID, ma.Multiaddr, error) { +func extractPeerInfo(peerma string) (lpeer.ID, ma.Multiaddr, error) { log.Debug("Extracting peer info from ", peerma) ipfsaddr, err := ma.NewMultiaddr(peerma) @@ -153,9 +165,10 @@ func ExtractPeerInfo(peerma string) (lpeer.ID, ma.Multiaddr, error) { // Connect adds the given multiaddress to p's Peerstore and opens a stream // with the peer at that multiaddress if the multiaddress is valid, otherwise -// returns error. +// returns error. On success the stream and corresponding multiaddress are +// added to this peer's subnet. func (p *Peer) Connect(peerma string) (net.Stream, error) { - peerid, targetAddr, err := ExtractPeerInfo(peerma) + peerid, targetAddr, err := extractPeerInfo(peerma) if err != nil { return nil, err } @@ -170,6 +183,21 @@ func (p *Peer) Connect(peerma string) (net.Stream, error) { // Open a stream with the peer stream, err := p.NewStream(context.Background(), peerid, CumulusProtocol) + if err != nil { + return nil, err + } + + mAddr, err := ma.NewMultiaddr(peerma) + if err != nil { + stream.Close() + return nil, err + } + + err = p.subnet.AddPeer(mAddr, stream) + if err != nil { + stream.Close() + return nil, err + } return stream, err } diff --git a/peer/peer_test.go b/peer/peer_test.go index 0b8f6fd..820eba9 100644 --- a/peer/peer_test.go +++ b/peer/peer_test.go @@ -58,7 +58,7 @@ func TestNewInvalidIP(t *testing.T) { func TestExtractPeerInfoValidMultiAddr(t *testing.T) { peerma := "/ip4/127.0.0.1/tcp/8765/ipfs/QmQdfp9Ug4MoLRsBToDPN2aQhg2jPtmmA8UidQUTXGjZcy" - pid, ma, err := ExtractPeerInfo(peerma) + pid, ma, err := extractPeerInfo(peerma) if err != nil { t.Fail() @@ -75,7 +75,7 @@ func TestExtractPeerInfoValidMultiAddr(t *testing.T) { func TestExtractPeerInfoInvalidIP(t *testing.T) { peerma := "/ip4/203.532.211.5/tcp/8765/ipfs/Qmb89FuJ8UG3dpgUqEYu9eUqK474uP3mx32WnQ7kePXp8N" - _, _, err := ExtractPeerInfo(peerma) + _, _, err := extractPeerInfo(peerma) if err == nil { t.Fail() diff --git a/subnet/subnet.go b/subnet/subnet.go new file mode 100644 index 0000000..6c66ae3 --- /dev/null +++ b/subnet/subnet.go @@ -0,0 +1,72 @@ +package subnet + +import ( + "errors" + + net "github.com/libp2p/go-libp2p-net" + ma "github.com/multiformats/go-multiaddr" + msg "github.com/ubclaunchpad/cumulus/message" +) + +const ( + // DefaultMaxPeers is the maximum number of peers a peer can be in + // communication with at any given time. + DefaultMaxPeers = 6 +) + +// Subnet represents the set of peers a peer is connected to at any given time +type Subnet struct { + peers map[ma.Multiaddr]net.Stream + maxPeers uint16 + numPeers uint16 +} + +// New returns a pointer to an empty Subnet with the given maxPeers. +func New(maxPeers uint16) *Subnet { + p := make(map[ma.Multiaddr]net.Stream) + sn := Subnet{maxPeers: maxPeers, numPeers: 0, peers: p} + return &sn +} + +// AddPeer adds a peer's multiaddress and the corresponding stream between the +// local peer and the remote peer to the subnet. If the subnet is already +// full, or the multiaddress is not valid, returns error. +func (sn *Subnet) AddPeer(mAddr ma.Multiaddr, stream net.Stream) error { + if sn.isFull() { + return errors.New("Cannot insert new mapping, Subnet is already full") + } + + // Validate the multiaddress + mAddr, err := ma.NewMultiaddr(mAddr.String()) + if err != nil { + return err + } + + sn.peers[mAddr] = stream + sn.numPeers++ + return nil +} + +// RemovePeer removes the mapping with the key mAddr from the subnet if it +// exists. +func (sn *Subnet) RemovePeer(mAddr ma.Multiaddr) { + delete(sn.peers, mAddr) + sn.numPeers-- +} + +// isFull returns true if the number of peers in the sunbet is at or over the +// limit set for that subnet, otherwise returns false. +func (sn *Subnet) isFull() bool { + return sn.numPeers >= sn.maxPeers +} + +// Broadcast sends information to all peers we are currently connected to +func (sn *Subnet) Broadcast(m msg.Message) error { + return errors.New("Function not implemented") +} + +// Listen listens to all peers we are currently connected to +// Call appropriate routines in response to new messages +func (sn *Subnet) Listen() { + panic("Function not implemented") +} From c0791d41b8ebed9b6da99e3a51309701aefa0903 Mon Sep 17 00:00:00 2001 From: Bruno Bachmann Date: Sat, 13 May 2017 14:19:13 -0700 Subject: [PATCH 03/37] Revert to older greeting message --- main.go | 2 +- peer/peer.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 70c9ca0..44b7ecc 100644 --- a/main.go +++ b/main.go @@ -47,7 +47,7 @@ func main() { } // Send a message to the peer - _, err = stream.Write([]byte("Hello, world!")) + _, err = stream.Write([]byte("Hello, world!\n")) if err != nil { log.Fatal(err) } diff --git a/peer/peer.go b/peer/peer.go index e59b090..f3501f8 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -94,6 +94,7 @@ func New(ip string, port int) (*Peer, error) { // We may want to implement another type of StreamHandler in the future. func (p *Peer) Receive(s net.Stream) { log.Debug("Setting basic stream handler.") + defer s.Close() p.doCumulus(s) } From 3ee56a27e8cca0ba3c814a7a8661f334a9cc6de6 Mon Sep 17 00:00:00 2001 From: Bruno Bachmann Date: Tue, 16 May 2017 22:48:38 -0700 Subject: [PATCH 04/37] Add preliminary Message and Subnet implementations --- message/message.go | 46 +++++++++++++++++++++++++++++ peer/peer.go | 43 ++++++++++++++++++++++----- peer/peer_test.go | 4 +-- subnet/subnet.go | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 156 insertions(+), 9 deletions(-) create mode 100644 message/message.go create mode 100644 subnet/subnet.go diff --git a/message/message.go b/message/message.go new file mode 100644 index 0000000..66e8ef0 --- /dev/null +++ b/message/message.go @@ -0,0 +1,46 @@ +package message + +import "errors" + +// Message types +// NOTE: because of the way iota works, changing the order in which the +// following constants appear will change their values, which may affect the +// ability of your peer to communicate with others. +const ( + // Send the multiaddress of a peer to another peer + PeerInfo = iota + // Send information about a block that was just hashed + NewBlock = iota + // Request chunk of the blockchain from peer + RequestChunk = iota + // Advertise that we have a chunk of the blockchain + AdvertiseChunk = iota + // Send information about a new transaction to another peer + Transaction = iota +) + +// Message is a container for information and its type that is +// sent between Cumulus peers. +type Message struct { + msgType int + content []byte +} + +// New returns a pointer to a message initialized with a byte array +// of content and a message type, or an error if the type is not one +// of those defined above. +func New(c []byte, t int) (*Message, error) { + switch t { + case PeerInfo: + case NewBlock: + case RequestChunk: + case AdvertiseChunk: + case Transaction: + break + default: + return nil, errors.New("Invalid message type") + } + + m := &Message{msgType: t, content: c} + return m, nil +} diff --git a/peer/peer.go b/peer/peer.go index f3501f8..5f9f112 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -14,16 +14,16 @@ import ( swarm "github.com/libp2p/go-libp2p-swarm" bhost "github.com/libp2p/go-libp2p/p2p/host/basic" ma "github.com/multiformats/go-multiaddr" + log "github.com/sirupsen/logrus" + sn "github.com/ubclaunchpad/cumulus/subnet" ) const ( // DefaultPort is the TCP port hosts will communicate over if none is // provided DefaultPort = 8765 - // CumulusProtocol is the name of the protocol peers communicate over CumulusProtocol = "/cumulus/0.0.1" - // DefaultIP is the IP address new hosts will use if none if provided DefaultIP = "127.0.0.1" ) @@ -31,6 +31,7 @@ const ( // Peer is a cumulus Peer composed of a host type Peer struct { host.Host + subnet sn.Subnet } // New creates a Cumulus host with the given IP addr and TCP port. @@ -69,9 +70,11 @@ func New(ip string, port int) (*Peer, error) { return nil, err } + subnet := *sn.New(sn.DefaultMaxPeers) + // Actually create the host and peer with the network we just set up. host := bhost.New(netwrk) - peer := &Peer{Host: host} + peer := &Peer{Host: host, subnet: subnet} // Build host multiaddress hostAddr, _ := ma.NewMultiaddr(fmt.Sprintf("/ipfs/%s", @@ -89,12 +92,22 @@ func New(ip string, port int) (*Peer, error) { } // Receive is the function that gets called when a remote peer -// opens a new stream with the host that SetStreamHandler() is called on. +// opens a new stream with this peer. // This should be passed as the second argument to SetStreamHandler(). // We may want to implement another type of StreamHandler in the future. func (p *Peer) Receive(s net.Stream) { log.Debug("Setting basic stream handler.") defer s.Close() + + // Add the remote peer to this peer's subnet + // TODO: discuss this behaviour (for now refuse connections if subnet full) + remoteMA := s.Conn().RemoteMultiaddr() + err := p.subnet.AddPeer(remoteMA, s) + if err != nil { + log.Warnln(err) + } + defer p.subnet.RemovePeer(remoteMA) + p.doCumulus(s) } @@ -121,7 +134,7 @@ func (p *Peer) doCumulus(s net.Stream) { // given multiaddress. // Returns peer ID (esentially 46 character hash created by the peer) // and the peer's multiaddress in the form /ip4//tcp/. -func ExtractPeerInfo(peerma string) (lpeer.ID, ma.Multiaddr, error) { +func extractPeerInfo(peerma string) (lpeer.ID, ma.Multiaddr, error) { log.Debug("Extracting peer info from ", peerma) ipfsaddr, err := ma.NewMultiaddr(peerma) @@ -153,9 +166,10 @@ func ExtractPeerInfo(peerma string) (lpeer.ID, ma.Multiaddr, error) { // Connect adds the given multiaddress to p's Peerstore and opens a stream // with the peer at that multiaddress if the multiaddress is valid, otherwise -// returns error. +// returns error. On success the stream and corresponding multiaddress are +// added to this peer's subnet. func (p *Peer) Connect(peerma string) (net.Stream, error) { - peerid, targetAddr, err := ExtractPeerInfo(peerma) + peerid, targetAddr, err := extractPeerInfo(peerma) if err != nil { return nil, err } @@ -170,6 +184,21 @@ func (p *Peer) Connect(peerma string) (net.Stream, error) { // Open a stream with the peer stream, err := p.NewStream(context.Background(), peerid, CumulusProtocol) + if err != nil { + return nil, err + } + + mAddr, err := ma.NewMultiaddr(peerma) + if err != nil { + stream.Close() + return nil, err + } + + err = p.subnet.AddPeer(mAddr, stream) + if err != nil { + stream.Close() + return nil, err + } return stream, err } diff --git a/peer/peer_test.go b/peer/peer_test.go index 80feb27..4d0f4e3 100644 --- a/peer/peer_test.go +++ b/peer/peer_test.go @@ -58,7 +58,7 @@ func TestNewInvalidIP(t *testing.T) { func TestExtractPeerInfoValidMultiAddr(t *testing.T) { peerma := "/ip4/127.0.0.1/tcp/8765/ipfs/QmQdfp9Ug4MoLRsBToDPN2aQhg2jPtmmA8UidQUTXGjZcy" - pid, ma, err := ExtractPeerInfo(peerma) + pid, ma, err := extractPeerInfo(peerma) if err != nil { t.Fail() @@ -75,7 +75,7 @@ func TestExtractPeerInfoValidMultiAddr(t *testing.T) { func TestExtractPeerInfoInvalidIP(t *testing.T) { peerma := "/ip4/203.532.211.5/tcp/8765/ipfs/Qmb89FuJ8UG3dpgUqEYu9eUqK474uP3mx32WnQ7kePXp8N" - _, _, err := ExtractPeerInfo(peerma) + _, _, err := extractPeerInfo(peerma) if err == nil { t.Fail() diff --git a/subnet/subnet.go b/subnet/subnet.go new file mode 100644 index 0000000..6c66ae3 --- /dev/null +++ b/subnet/subnet.go @@ -0,0 +1,72 @@ +package subnet + +import ( + "errors" + + net "github.com/libp2p/go-libp2p-net" + ma "github.com/multiformats/go-multiaddr" + msg "github.com/ubclaunchpad/cumulus/message" +) + +const ( + // DefaultMaxPeers is the maximum number of peers a peer can be in + // communication with at any given time. + DefaultMaxPeers = 6 +) + +// Subnet represents the set of peers a peer is connected to at any given time +type Subnet struct { + peers map[ma.Multiaddr]net.Stream + maxPeers uint16 + numPeers uint16 +} + +// New returns a pointer to an empty Subnet with the given maxPeers. +func New(maxPeers uint16) *Subnet { + p := make(map[ma.Multiaddr]net.Stream) + sn := Subnet{maxPeers: maxPeers, numPeers: 0, peers: p} + return &sn +} + +// AddPeer adds a peer's multiaddress and the corresponding stream between the +// local peer and the remote peer to the subnet. If the subnet is already +// full, or the multiaddress is not valid, returns error. +func (sn *Subnet) AddPeer(mAddr ma.Multiaddr, stream net.Stream) error { + if sn.isFull() { + return errors.New("Cannot insert new mapping, Subnet is already full") + } + + // Validate the multiaddress + mAddr, err := ma.NewMultiaddr(mAddr.String()) + if err != nil { + return err + } + + sn.peers[mAddr] = stream + sn.numPeers++ + return nil +} + +// RemovePeer removes the mapping with the key mAddr from the subnet if it +// exists. +func (sn *Subnet) RemovePeer(mAddr ma.Multiaddr) { + delete(sn.peers, mAddr) + sn.numPeers-- +} + +// isFull returns true if the number of peers in the sunbet is at or over the +// limit set for that subnet, otherwise returns false. +func (sn *Subnet) isFull() bool { + return sn.numPeers >= sn.maxPeers +} + +// Broadcast sends information to all peers we are currently connected to +func (sn *Subnet) Broadcast(m msg.Message) error { + return errors.New("Function not implemented") +} + +// Listen listens to all peers we are currently connected to +// Call appropriate routines in response to new messages +func (sn *Subnet) Listen() { + panic("Function not implemented") +} From fdfde214686d713eac61afecbd153e92274ad455 Mon Sep 17 00:00:00 2001 From: Bruno Bachmann Date: Thu, 18 May 2017 22:50:58 -0700 Subject: [PATCH 05/37] Update subnet, peer, and message, refactor Note that I moved extractPeerInfo() down to near the bottom of peer.go (because it's not exported) --- glide.lock | 2 +- glide.yaml | 2 +- main.go | 5 +- message/message.go | 10 +++- peer/peer.go | 132 ++++++++++++++++++++++++++++----------------- peer/peer_test.go | 2 +- subnet/subnet.go | 21 +++++++- 7 files changed, 117 insertions(+), 57 deletions(-) diff --git a/glide.lock b/glide.lock index 9522746..06bc085 100644 --- a/glide.lock +++ b/glide.lock @@ -135,7 +135,7 @@ imports: version: b8f1996688ab586031517919b49b1967fca8d5d9 - name: github.com/satori/go.uuid version: 5bf94b69c6b68ee1b541973bb8e1144db23a194b -- name: github.com/Sirupsen/logrus +- name: github.com/sirupsen/logrus version: acfabf31db8f45a9174f54a0d48ea4d15627af4d - name: github.com/spaolacci/murmur3 version: 0d12bf811670bf6a1a63828dfbd003eded177fce diff --git a/glide.yaml b/glide.yaml index 6b0b13f..da8a2c7 100644 --- a/glide.yaml +++ b/glide.yaml @@ -10,4 +10,4 @@ import: subpackages: - p2p/host/basic - package: github.com/multiformats/go-multiaddr -- package: github.com/Sirupsen/logrus +- package: github.com/sirupsen/logrus diff --git a/main.go b/main.go index 44b7ecc..5ee7329 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,7 @@ import ( "flag" "io/ioutil" - log "github.com/Sirupsen/logrus" + log "github.com/sirupsen/logrus" "github.com/ubclaunchpad/cumulus/peer" ) @@ -60,5 +60,8 @@ func main() { log.Debugf("Peer %s read reply: %s", host.ID(), string(reply)) + log.Debug("Hanging...") + select {} + host.Close() } diff --git a/message/message.go b/message/message.go index 66e8ef0..bff0587 100644 --- a/message/message.go +++ b/message/message.go @@ -1,6 +1,9 @@ package message -import "errors" +import ( + "errors" + "fmt" +) // Message types // NOTE: because of the way iota works, changing the order in which the @@ -44,3 +47,8 @@ func New(c []byte, t int) (*Message, error) { m := &Message{msgType: t, content: c} return m, nil } + +// Bytes returns the given message in []byte format +func (m *Message) Bytes() []byte { + return []byte(fmt.Sprintf("%d:%s", m.msgType, string(m.content))) +} diff --git a/peer/peer.go b/peer/peer.go index 5f9f112..324babb 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -5,7 +5,6 @@ import ( "context" "fmt" - log "github.com/Sirupsen/logrus" crypto "github.com/libp2p/go-libp2p-crypto" host "github.com/libp2p/go-libp2p-host" net "github.com/libp2p/go-libp2p-net" @@ -15,6 +14,7 @@ import ( bhost "github.com/libp2p/go-libp2p/p2p/host/basic" ma "github.com/multiformats/go-multiaddr" log "github.com/sirupsen/logrus" + msg "github.com/ubclaunchpad/cumulus/message" sn "github.com/ubclaunchpad/cumulus/subnet" ) @@ -93,28 +93,29 @@ func New(ip string, port int) (*Peer, error) { // Receive is the function that gets called when a remote peer // opens a new stream with this peer. -// This should be passed as the second argument to SetStreamHandler(). -// We may want to implement another type of StreamHandler in the future. +// This should be passed as the second argument to SetStreamHandler() after this +// peer is initialized. func (p *Peer) Receive(s net.Stream) { - log.Debug("Setting basic stream handler.") defer s.Close() + // Get remote peer's full multiaddress + remoteMA, err := makeMultiaddr( + s.Conn().RemoteMultiaddr(), s.Conn().RemotePeer()) + if err != nil { + log.Fatal("Failed to obtain valid remote peer multiaddress") + } + // Add the remote peer to this peer's subnet - // TODO: discuss this behaviour (for now refuse connections if subnet full) - remoteMA := s.Conn().RemoteMultiaddr() - err := p.subnet.AddPeer(remoteMA, s) + err = p.subnet.AddPeer(remoteMA, s) if err != nil { - log.Warnln(err) + // Subnet is full, advertise other available peers and then close + // the stream + log.Debug("Peer subnet full. Advertising peers...") + p.advertisePeers(s) + return } defer p.subnet.RemovePeer(remoteMA) - p.doCumulus(s) -} - -// Communicate with peers. -// TODO: Update this to do something useful. For now it just reads from the -// stream and writes back what it read. -func (p *Peer) doCumulus(s net.Stream) { buf := bufio.NewReader(s) str, err := buf.ReadString('\n') if err != nil { @@ -130,40 +131,6 @@ func (p *Peer) doCumulus(s net.Stream) { } } -// ExtractPeerInfo extracts the peer ID and multiaddress from the -// given multiaddress. -// Returns peer ID (esentially 46 character hash created by the peer) -// and the peer's multiaddress in the form /ip4//tcp/. -func extractPeerInfo(peerma string) (lpeer.ID, ma.Multiaddr, error) { - log.Debug("Extracting peer info from ", peerma) - - ipfsaddr, err := ma.NewMultiaddr(peerma) - if err != nil { - return "-", nil, err - } - - // Cannot throw error when passed P_IPFS - pid, err := ipfsaddr.ValueForProtocol(ma.P_IPFS) - if err != nil { - return "-", nil, err - } - - // Cannot return error if no error was returned in ValueForProtocol - peerid, _ := lpeer.IDB58Decode(pid) - - // Decapsulate the /ipfs/ part from the target - // /ip4//ipfs/ becomes /ip4/ - targetPeerAddr, err := ma.NewMultiaddr( - fmt.Sprintf("/ipfs/%s", lpeer.IDB58Encode(peerid))) - if err != nil { - return "-", nil, err - } - - trgtAddr := ipfsaddr.Decapsulate(targetPeerAddr) - - return peerid, trgtAddr, nil -} - // Connect adds the given multiaddress to p's Peerstore and opens a stream // with the peer at that multiaddress if the multiaddress is valid, otherwise // returns error. On success the stream and corresponding multiaddress are @@ -178,7 +145,7 @@ func (p *Peer) Connect(peerma string) (net.Stream, error) { p.Peerstore().AddAddr(peerid, targetAddr, pstore.PermanentAddrTTL) log.Debug("Connected to Cumulus Peer:") - log.Debug("Peer ID:", peerid.Pretty()) + log.Debugf("Peer ID: %s", peerid.Pretty()) log.Debug("Peer Address:", targetAddr) // Open a stream with the peer @@ -202,3 +169,68 @@ func (p *Peer) Connect(peerma string) (net.Stream, error) { return stream, err } + +// ExtractPeerInfo extracts the peer ID and multiaddress from the +// given multiaddress. +// Returns peer ID (esentially 46 character hash created by the peer) +// and the peer's multiaddress in the form /ip4//tcp/. +func extractPeerInfo(peerma string) (lpeer.ID, ma.Multiaddr, error) { + log.Debug("Extracting peer info from ", peerma) + + ipfsaddr, err := ma.NewMultiaddr(peerma) + if err != nil { + return "-", nil, err + } + + // Cannot throw error when passed P_IPFS + pid, err := ipfsaddr.ValueForProtocol(ma.P_IPFS) + if err != nil { + return "-", nil, err + } + + // Cannot return error if no error was returned in ValueForProtocol + peerid, _ := lpeer.IDB58Decode(pid) + + // Decapsulate the /ipfs/ part from the target + // /ip4//ipfs/ becomes /ip4/ + targetPeerAddr, err := ma.NewMultiaddr( + fmt.Sprintf("/ipfs/%s", lpeer.IDB58Encode(peerid))) + if err != nil { + return "-", nil, err + } + + trgtAddr := ipfsaddr.Decapsulate(targetPeerAddr) + + return peerid, trgtAddr, nil +} + +// advertisePeers writes messages into the given stream advertising the +// multiaddress of each peer in this peer's subnet. +func (p *Peer) advertisePeers(s net.Stream) { + mAddrs := p.subnet.Multiaddrs() + log.Debug("Peers on this subnet: ") + for mAddr := range mAddrs { + mAddrString := string(mAddr) + log.Debug("\t", mAddrString) + message, msgErr := msg.New([]byte(mAddrString), msg.PeerInfo) + if msgErr != nil { + log.Error("Failed to create message") + return + } + _, msgErr = s.Write(message.Bytes()) + if msgErr != nil { + log.Errorf("Failed to send message to %s", string(mAddr)) + } + } +} + +// makeMultiaddr creates a Multiaddress from the given Multiaddress (of the form +// /ip4//tcp/) and the peer id (a hash) and turn them +// into one Multiaddress. Will return error if Multiaddress is invalid. +func makeMultiaddr(iAddr ma.Multiaddr, pid lpeer.ID) (ma.Multiaddr, error) { + strAddr := iAddr.String() + strID := pid.Pretty() + strMA := fmt.Sprintf("%s/ipfs/%s", strAddr, strID) + mAddr, err := ma.NewMultiaddr(strMA) + return mAddr, err +} diff --git a/peer/peer_test.go b/peer/peer_test.go index 4d0f4e3..820eba9 100644 --- a/peer/peer_test.go +++ b/peer/peer_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - log "github.com/Sirupsen/logrus" + log "github.com/sirupsen/logrus" ) func TestMain(t *testing.T) { diff --git a/subnet/subnet.go b/subnet/subnet.go index 6c66ae3..c26e28f 100644 --- a/subnet/subnet.go +++ b/subnet/subnet.go @@ -5,6 +5,7 @@ import ( net "github.com/libp2p/go-libp2p-net" ma "github.com/multiformats/go-multiaddr" + log "github.com/sirupsen/logrus" msg "github.com/ubclaunchpad/cumulus/message" ) @@ -42,14 +43,21 @@ func (sn *Subnet) AddPeer(mAddr ma.Multiaddr, stream net.Stream) error { return err } - sn.peers[mAddr] = stream - sn.numPeers++ + // Check if it's already in this subnet + if sn.peers[mAddr] != nil { + log.Debugf("Peer %s is already in subnet", mAddr.String()) + } else { + log.Debugf("Adding peer %s to subnet", mAddr.String()) + sn.peers[mAddr] = stream + sn.numPeers++ + } return nil } // RemovePeer removes the mapping with the key mAddr from the subnet if it // exists. func (sn *Subnet) RemovePeer(mAddr ma.Multiaddr) { + log.Debugf("Removing peer %s from subnet", mAddr.String()) delete(sn.peers, mAddr) sn.numPeers-- } @@ -60,6 +68,15 @@ func (sn *Subnet) isFull() bool { return sn.numPeers >= sn.maxPeers } +// Multiaddrs returns a list of all multiaddresses contined in this subnet +func (sn *Subnet) Multiaddrs() []ma.Multiaddr { + mAddrs := make([]ma.Multiaddr, 0, len(sn.peers)) + for mAddr := range sn.peers { + mAddrs = append(mAddrs, mAddr) + } + return mAddrs +} + // Broadcast sends information to all peers we are currently connected to func (sn *Subnet) Broadcast(m msg.Message) error { return errors.New("Function not implemented") From 156dd851399ca0b0a9379b54003f1711e8d969c0 Mon Sep 17 00:00:00 2001 From: Bruno Bachmann Date: Sat, 20 May 2017 12:49:02 -0700 Subject: [PATCH 06/37] Add message parsing and refactor --- main.go | 3 --- message/message.go | 27 +++++++++++++++++++++++---- peer/peer.go | 42 ++++++++++++++++++++++++++++++++++-------- subnet/subnet.go | 12 ------------ 4 files changed, 57 insertions(+), 27 deletions(-) diff --git a/main.go b/main.go index 5ee7329..cabbb40 100644 --- a/main.go +++ b/main.go @@ -60,8 +60,5 @@ func main() { log.Debugf("Peer %s read reply: %s", host.ID(), string(reply)) - log.Debug("Hanging...") - select {} - host.Close() } diff --git a/message/message.go b/message/message.go index bff0587..11227bc 100644 --- a/message/message.go +++ b/message/message.go @@ -1,8 +1,9 @@ package message import ( + "encoding/json" "errors" - "fmt" + "strings" ) // Message types @@ -12,6 +13,8 @@ import ( const ( // Send the multiaddress of a peer to another peer PeerInfo = iota + // Request addressess of peers in the remote peer's subnet + RequestPeerInfo = iota // Send information about a block that was just hashed NewBlock = iota // Request chunk of the blockchain from peer @@ -48,7 +51,23 @@ func New(c []byte, t int) (*Message, error) { return m, nil } -// Bytes returns the given message in []byte format -func (m *Message) Bytes() []byte { - return []byte(fmt.Sprintf("%d:%s", m.msgType, string(m.content))) +// Bytes returns JSON representation of message as a byte array, or error if +// message cannot be marshalled. +func (m *Message) Bytes() ([]byte, error) { + return json.Marshal(m) +} + +// FromString parses a message in the form of a string and returns a pointer +// to a new Message struct made from the contents of the string. Returns error +// if string is malformed. +func FromString(s string) (*Message, error) { + var msg Message + s = strings.TrimSpace(s) + err := json.Unmarshal([]byte(s), &msg) + return &msg, err +} + +// Type returns msgType for message +func (m *Message) Type() int { + return m.msgType } diff --git a/peer/peer.go b/peer/peer.go index 324babb..0189912 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -3,6 +3,7 @@ package peer import ( "bufio" "context" + "errors" "fmt" crypto "github.com/libp2p/go-libp2p-crypto" @@ -117,18 +118,23 @@ func (p *Peer) Receive(s net.Stream) { defer p.subnet.RemovePeer(remoteMA) buf := bufio.NewReader(s) - str, err := buf.ReadString('\n') + strMsg, err := buf.ReadString('\n') // TODO: set timeout here if err != nil { log.Error(err) return } - log.Debugf("Peer %s read: %s", p.ID(), str) - - _, err = s.Write([]byte(str)) + // Turn the string into a message we can deal with + message, err := msg.FromString(strMsg) if err != nil { log.Error(err) + return } + + log.Debugf("Peer %s message:\n%s", p.ID(), strMsg) + + // Respond to message + p.handleMessage(*message, s) } // Connect adds the given multiaddress to p's Peerstore and opens a stream @@ -170,6 +176,11 @@ func (p *Peer) Connect(peerma string) (net.Stream, error) { return stream, err } +// Broadcast sends message to all peers this peer is currently connected to +func (p *Peer) Broadcast(m msg.Message) error { + return errors.New("Function not implemented") +} + // ExtractPeerInfo extracts the peer ID and multiaddress from the // given multiaddress. // Returns peer ID (esentially 46 character hash created by the peer) @@ -212,13 +223,18 @@ func (p *Peer) advertisePeers(s net.Stream) { for mAddr := range mAddrs { mAddrString := string(mAddr) log.Debug("\t", mAddrString) - message, msgErr := msg.New([]byte(mAddrString), msg.PeerInfo) - if msgErr != nil { + message, err := msg.New([]byte(mAddrString), msg.PeerInfo) + if err != nil { log.Error("Failed to create message") return } - _, msgErr = s.Write(message.Bytes()) - if msgErr != nil { + msgBytes, err := message.Bytes() + if err != nil { + log.Error("Failed to marshal message") + return + } + _, err = s.Write(msgBytes) + if err != nil { log.Errorf("Failed to send message to %s", string(mAddr)) } } @@ -234,3 +250,13 @@ func makeMultiaddr(iAddr ma.Multiaddr, pid lpeer.ID) (ma.Multiaddr, error) { mAddr, err := ma.NewMultiaddr(strMA) return mAddr, err } + +func (p *Peer) handleMessage(m msg.Message, s net.Stream) { + switch m.Type() { + case msg.RequestPeerInfo: + p.advertisePeers(s) + break + default: + // Do nothing. WHEOOO! + } +} diff --git a/subnet/subnet.go b/subnet/subnet.go index c26e28f..e289e3e 100644 --- a/subnet/subnet.go +++ b/subnet/subnet.go @@ -6,7 +6,6 @@ import ( net "github.com/libp2p/go-libp2p-net" ma "github.com/multiformats/go-multiaddr" log "github.com/sirupsen/logrus" - msg "github.com/ubclaunchpad/cumulus/message" ) const ( @@ -76,14 +75,3 @@ func (sn *Subnet) Multiaddrs() []ma.Multiaddr { } return mAddrs } - -// Broadcast sends information to all peers we are currently connected to -func (sn *Subnet) Broadcast(m msg.Message) error { - return errors.New("Function not implemented") -} - -// Listen listens to all peers we are currently connected to -// Call appropriate routines in response to new messages -func (sn *Subnet) Listen() { - panic("Function not implemented") -} From c5a326ac34b341429cd8e0a6f9aa73a84fd13715 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Sun, 21 May 2017 16:55:49 -0700 Subject: [PATCH 07/37] Create message types and structs --- glide.lock | 2 +- glide.yaml | 2 +- main.go | 2 +- message/message.go | 111 +++++++++++++++++++++++---------------------- peer/peer.go | 2 +- subnet/subnet.go | 2 +- 6 files changed, 62 insertions(+), 59 deletions(-) diff --git a/glide.lock b/glide.lock index 06bc085..9522746 100644 --- a/glide.lock +++ b/glide.lock @@ -135,7 +135,7 @@ imports: version: b8f1996688ab586031517919b49b1967fca8d5d9 - name: github.com/satori/go.uuid version: 5bf94b69c6b68ee1b541973bb8e1144db23a194b -- name: github.com/sirupsen/logrus +- name: github.com/Sirupsen/logrus version: acfabf31db8f45a9174f54a0d48ea4d15627af4d - name: github.com/spaolacci/murmur3 version: 0d12bf811670bf6a1a63828dfbd003eded177fce diff --git a/glide.yaml b/glide.yaml index da8a2c7..6b0b13f 100644 --- a/glide.yaml +++ b/glide.yaml @@ -10,4 +10,4 @@ import: subpackages: - p2p/host/basic - package: github.com/multiformats/go-multiaddr -- package: github.com/sirupsen/logrus +- package: github.com/Sirupsen/logrus diff --git a/main.go b/main.go index cabbb40..44b7ecc 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,7 @@ import ( "flag" "io/ioutil" - log "github.com/sirupsen/logrus" + log "github.com/Sirupsen/logrus" "github.com/ubclaunchpad/cumulus/peer" ) diff --git a/message/message.go b/message/message.go index 11227bc..685e880 100644 --- a/message/message.go +++ b/message/message.go @@ -1,73 +1,76 @@ package message import ( - "encoding/json" - "errors" - "strings" + "encoding/gob" + "io" +) + +type ( + // Type specifies the type of a message. + Type int + // ResourceType specifies the type of a resource in a message. + ResourceType int +) + +const ( + // MessageRequest messages ask a peer for a resource. + MessageRequest Type = iota + // MessageResponse messages repond to a request message with an error or a resource. + MessageResponse + // MessagePush messages proactively send a resource to a peer. + MessagePush ) -// Message types -// NOTE: because of the way iota works, changing the order in which the -// following constants appear will change their values, which may affect the -// ability of your peer to communicate with others. const ( - // Send the multiaddress of a peer to another peer - PeerInfo = iota - // Request addressess of peers in the remote peer's subnet - RequestPeerInfo = iota - // Send information about a block that was just hashed - NewBlock = iota - // Request chunk of the blockchain from peer - RequestChunk = iota - // Advertise that we have a chunk of the blockchain - AdvertiseChunk = iota - // Send information about a new transaction to another peer - Transaction = iota + // ResourcePeerInfo resources contain a list of peers. + ResourcePeerInfo ResourceType = iota + // ResourceBlock resources contain a block in the blockchain. + ResourceBlock + // ResourceTransaction resources contain a transaction to add to the blockchain. + ResourceTransaction ) -// Message is a container for information and its type that is -// sent between Cumulus peers. +// Message is a container for messages, containing a type and either a Request, +// Response, or Push in the payload. type Message struct { - msgType int - content []byte + Type Type + Payload interface{} } -// New returns a pointer to a message initialized with a byte array -// of content and a message type, or an error if the type is not one -// of those defined above. -func New(c []byte, t int) (*Message, error) { - switch t { - case PeerInfo: - case NewBlock: - case RequestChunk: - case AdvertiseChunk: - case Transaction: - break - default: - return nil, errors.New("Invalid message type") - } +// Request is a container for a request payload, containing a unique request ID, +// the resource type we are requesting, and a Params field specifying request +// parameters. PeerInfo requests should send all info of all peers. Block requests +// should specify block number in parameters. +type Request struct { + ID string + ResourceType ResourceType + Params map[string]interface{} +} - m := &Message{msgType: t, content: c} - return m, nil +// Response is a container for a response payload, containing the unique request +// ID of the request prompting it, an Error (if one occurred), and the requested +// resource (if no error occurred). +type Response struct { + ID string + Error error + Resource interface{} } -// Bytes returns JSON representation of message as a byte array, or error if -// message cannot be marshalled. -func (m *Message) Bytes() ([]byte, error) { - return json.Marshal(m) +// Push is a container for a push payload, containing a resource proactively sent +// to us by another peer. +type Push struct { + ResourceType ResourceType + Resource interface{} } -// FromString parses a message in the form of a string and returns a pointer -// to a new Message struct made from the contents of the string. Returns error -// if string is malformed. -func FromString(s string) (*Message, error) { - var msg Message - s = strings.TrimSpace(s) - err := json.Unmarshal([]byte(s), &msg) - return &msg, err +// Encode encodes and writes the Message into the given Writer. +func (m *Message) Encode(w io.Writer) error { + return gob.NewEncoder(w).Encode(m) } -// Type returns msgType for message -func (m *Message) Type() int { - return m.msgType +// Decode decodes a message from a Reader and returns it. +func Decode(r io.Reader) (*Message, error) { + var m Message + err := gob.NewDecoder(r).Decode(&m) + return &m, err } diff --git a/peer/peer.go b/peer/peer.go index 0189912..c03634e 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" + log "github.com/Sirupsen/logrus" crypto "github.com/libp2p/go-libp2p-crypto" host "github.com/libp2p/go-libp2p-host" net "github.com/libp2p/go-libp2p-net" @@ -14,7 +15,6 @@ import ( swarm "github.com/libp2p/go-libp2p-swarm" bhost "github.com/libp2p/go-libp2p/p2p/host/basic" ma "github.com/multiformats/go-multiaddr" - log "github.com/sirupsen/logrus" msg "github.com/ubclaunchpad/cumulus/message" sn "github.com/ubclaunchpad/cumulus/subnet" ) diff --git a/subnet/subnet.go b/subnet/subnet.go index e289e3e..30e5b0f 100644 --- a/subnet/subnet.go +++ b/subnet/subnet.go @@ -3,9 +3,9 @@ package subnet import ( "errors" + log "github.com/Sirupsen/logrus" net "github.com/libp2p/go-libp2p-net" ma "github.com/multiformats/go-multiaddr" - log "github.com/sirupsen/logrus" ) const ( From d3d17b4227c15a8a5b7dd6568b60d415ccb3bdc6 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Sun, 21 May 2017 17:27:14 -0700 Subject: [PATCH 08/37] Update peer package to use new message structure --- message/message.go | 16 +++++++++---- peer/peer.go | 60 +++++++++++++++++----------------------------- 2 files changed, 34 insertions(+), 42 deletions(-) diff --git a/message/message.go b/message/message.go index 685e880..f2fd34a 100644 --- a/message/message.go +++ b/message/message.go @@ -37,6 +37,14 @@ type Message struct { Payload interface{} } +// New returns a new Message. +func New(t Type, payload interface{}) *Message { + return &Message{ + Type: t, + Payload: payload, + } +} + // Request is a container for a request payload, containing a unique request ID, // the resource type we are requesting, and a Params field specifying request // parameters. PeerInfo requests should send all info of all peers. Block requests @@ -63,13 +71,13 @@ type Push struct { Resource interface{} } -// Encode encodes and writes the Message into the given Writer. -func (m *Message) Encode(w io.Writer) error { +// Write encodes and writes the Message into the given Writer. +func (m *Message) Write(w io.Writer) error { return gob.NewEncoder(w).Encode(m) } -// Decode decodes a message from a Reader and returns it. -func Decode(r io.Reader) (*Message, error) { +// Read decodes a message from a Reader and returns it. +func Read(r io.Reader) (*Message, error) { var m Message err := gob.NewDecoder(r).Decode(&m) return &m, err diff --git a/peer/peer.go b/peer/peer.go index c03634e..b65aa99 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -1,21 +1,21 @@ package peer import ( - "bufio" "context" "errors" "fmt" + "time" log "github.com/Sirupsen/logrus" - crypto "github.com/libp2p/go-libp2p-crypto" - host "github.com/libp2p/go-libp2p-host" - net "github.com/libp2p/go-libp2p-net" + "github.com/libp2p/go-libp2p-crypto" + "github.com/libp2p/go-libp2p-host" + "github.com/libp2p/go-libp2p-net" lpeer "github.com/libp2p/go-libp2p-peer" pstore "github.com/libp2p/go-libp2p-peerstore" - swarm "github.com/libp2p/go-libp2p-swarm" + "github.com/libp2p/go-libp2p-swarm" bhost "github.com/libp2p/go-libp2p/p2p/host/basic" ma "github.com/multiformats/go-multiaddr" - msg "github.com/ubclaunchpad/cumulus/message" + "github.com/ubclaunchpad/cumulus/message" sn "github.com/ubclaunchpad/cumulus/subnet" ) @@ -27,6 +27,8 @@ const ( CumulusProtocol = "/cumulus/0.0.1" // DefaultIP is the IP address new hosts will use if none if provided DefaultIP = "127.0.0.1" + // Timeout is the time after which reads from a stream will timeout + Timeout = time.Second * 30 ) // Peer is a cumulus Peer composed of a host @@ -117,24 +119,20 @@ func (p *Peer) Receive(s net.Stream) { } defer p.subnet.RemovePeer(remoteMA) - buf := bufio.NewReader(s) - strMsg, err := buf.ReadString('\n') // TODO: set timeout here + err = s.SetDeadline(time.Now().Add(Timeout)) if err != nil { - log.Error(err) - return + log.WithError(err).Error("Failed to set read deadline on stream") } - - // Turn the string into a message we can deal with - message, err := msg.FromString(strMsg) + msg, err := message.Read(s) if err != nil { - log.Error(err) + log.WithError(err).Error("Error reading from the stream") return } - log.Debugf("Peer %s message:\n%s", p.ID(), strMsg) + log.Debugf("Peer %s message:\n%s", p.ID(), msg.Type) // Respond to message - p.handleMessage(*message, s) + p.handleMessage(msg, s) } // Connect adds the given multiaddress to p's Peerstore and opens a stream @@ -177,7 +175,7 @@ func (p *Peer) Connect(peerma string) (net.Stream, error) { } // Broadcast sends message to all peers this peer is currently connected to -func (p *Peer) Broadcast(m msg.Message) error { +func (p *Peer) Broadcast(m message.Message) error { return errors.New("Function not implemented") } @@ -218,25 +216,11 @@ func extractPeerInfo(peerma string) (lpeer.ID, ma.Multiaddr, error) { // advertisePeers writes messages into the given stream advertising the // multiaddress of each peer in this peer's subnet. func (p *Peer) advertisePeers(s net.Stream) { - mAddrs := p.subnet.Multiaddrs() log.Debug("Peers on this subnet: ") - for mAddr := range mAddrs { - mAddrString := string(mAddr) - log.Debug("\t", mAddrString) - message, err := msg.New([]byte(mAddrString), msg.PeerInfo) - if err != nil { - log.Error("Failed to create message") - return - } - msgBytes, err := message.Bytes() - if err != nil { - log.Error("Failed to marshal message") - return - } - _, err = s.Write(msgBytes) - if err != nil { - log.Errorf("Failed to send message to %s", string(mAddr)) - } + msg := message.New(message.MessageResponse, p.subnet.Multiaddrs()) + err := msg.Write(s) + if err != nil { + log.WithError(err).Error("Error writing PeerInfo message to stream") } } @@ -251,9 +235,9 @@ func makeMultiaddr(iAddr ma.Multiaddr, pid lpeer.ID) (ma.Multiaddr, error) { return mAddr, err } -func (p *Peer) handleMessage(m msg.Message, s net.Stream) { - switch m.Type() { - case msg.RequestPeerInfo: +func (p *Peer) handleMessage(m *message.Message, s net.Stream) { + switch m.Type { + case message.MessageRequest: p.advertisePeers(s) break default: From 3c898b286dd8aa204011cb9ea5d4dfe6a58ad878 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Sun, 21 May 2017 17:33:00 -0700 Subject: [PATCH 09/37] udpate import --- peer/peer_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peer/peer_test.go b/peer/peer_test.go index 820eba9..4d0f4e3 100644 --- a/peer/peer_test.go +++ b/peer/peer_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - log "github.com/sirupsen/logrus" + log "github.com/Sirupsen/logrus" ) func TestMain(t *testing.T) { From f49345d7923e70dfdbf9bd146f8da6a005ba24a0 Mon Sep 17 00:00:00 2001 From: Bruno Bachmann Date: Mon, 22 May 2017 09:50:23 -0700 Subject: [PATCH 10/37] Update gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 771aa14..9ec6311 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,6 @@ _testmain.go # Glide dependencies vendor + +# Text Editors and IDEs +.vscode From 7348ad61cf1cb290189c710807c9a4c1ee92cbc2 Mon Sep 17 00:00:00 2001 From: Bruno Bachmann Date: Tue, 23 May 2017 23:07:11 -0700 Subject: [PATCH 11/37] Add protocol errors, update message, peer, and subnet --- errors/error.go | 65 +++++++++++++++ main.go | 23 +++--- message/message.go | 46 +++++++++++ peer/peer.go | 200 ++++++++++++++++++++++++++++++++------------- subnet/subnet.go | 26 +++--- 5 files changed, 280 insertions(+), 80 deletions(-) create mode 100644 errors/error.go diff --git a/errors/error.go b/errors/error.go new file mode 100644 index 0000000..fc3688c --- /dev/null +++ b/errors/error.go @@ -0,0 +1,65 @@ +package errors + +import "fmt" + +const ( + // InvalidMessageType occurs when a message is received with an unknown + // MesssageType value. + InvalidMessageType = 401 + // InvalidResourceType occurs when a request is received with an unknown + // ResourceType value. + InvalidResourceType = 402 + // SubnetFull occurs when a stream is opened with a peer who's Subnet is + // already full. + SubnetFull = 501 + // NotImplemented occurs when a message or request is received whos response + // requires functionality that does not yet exist. + NotImplemented = 502 +) + +const ( + invalidMessageTypeMsg = "Invalid message type" + invalidResourceTypeMsg = "Invalid resource type" + subnetFullMsg = "Failed to add peer to subnet (peer subnet full)" + notImplementedMsg = "Functionality not yet implemented" +) + +// ProtocolError is a container for error messages returned in Peer +// Response bodies. +type ProtocolError struct { + Code int + Message string +} + +// New returns new ProtocolError with the given parameters. +// code argument should be one of the error codes defined above. +func New(code int) *ProtocolError { + var msg string + + switch code { + case SubnetFull: + msg = subnetFullMsg + break + case InvalidMessageType: + msg = invalidMessageTypeMsg + break + case InvalidResourceType: + msg = invalidResourceTypeMsg + break + case NotImplemented: + msg = notImplementedMsg + break + default: + panic("Attempt to create error with invalid error code") + } + + return &ProtocolError{ + Code: code, + Message: msg, + } +} + +// Error returns the string representation of the given ProtocolError +func (e ProtocolError) Error() string { + return fmt.Sprintf("%d: %s", e.Code, e.Message) +} diff --git a/main.go b/main.go index 44b7ecc..0892da8 100644 --- a/main.go +++ b/main.go @@ -2,14 +2,16 @@ package main import ( "flag" - "io/ioutil" log "github.com/Sirupsen/logrus" + "github.com/google/uuid" + "github.com/ubclaunchpad/cumulus/message" "github.com/ubclaunchpad/cumulus/peer" ) func main() { log.Info("Starting Cumulus Peer") + message.RegisterGobTypes() // Get and parse command line arguments // targetPeer is a Multiaddr representing the target peer to connect to @@ -46,19 +48,18 @@ func main() { log.Fatal(err) } - // Send a message to the peer - _, err = stream.Write([]byte("Hello, world!\n")) - if err != nil { - log.Fatal(err) + // Request peer info + request := message.Request{ + ID: uuid.New().String(), + ResourceType: message.ResourcePeerInfo, + Params: nil, } - - // Read the reply from the peer - reply, err := ioutil.ReadAll(stream) + response, err := host.Request(request, stream) if err != nil { - log.Fatal(err) + log.WithError(err).Error("Error writing message to stream") + return } - log.Debugf("Peer %s read reply: %s", host.ID(), string(reply)) - + host.HandleMessage(*message.NewResponseMessage("", nil, response), stream) host.Close() } diff --git a/message/message.go b/message/message.go index f2fd34a..69cca67 100644 --- a/message/message.go +++ b/message/message.go @@ -3,6 +3,9 @@ package message import ( "encoding/gob" "io" + + "github.com/google/uuid" + ma "github.com/multiformats/go-multiaddr" ) type ( @@ -55,6 +58,17 @@ type Request struct { Params map[string]interface{} } +// NewRequestMessage returns a new Message continaing a request with the given +// parameters. +func NewRequestMessage(rt ResourceType, p map[string]interface{}) *Message { + req := Request{ + ID: uuid.New().String(), + ResourceType: rt, + Params: p, + } + return New(MessageRequest, req) +} + // Response is a container for a response payload, containing the unique request // ID of the request prompting it, an Error (if one occurred), and the requested // resource (if no error occurred). @@ -64,6 +78,18 @@ type Response struct { Resource interface{} } +// NewResponseMessage returns a new Message continaing a response with the given +// parameters. ResponseMessages should have the same ID as that of the request +// message they are a response to. +func NewResponseMessage(id string, err error, resource interface{}) *Message { + res := &Response{ + ID: id, + Error: err, + Resource: resource, + } + return New(MessageResponse, res) +} + // Push is a container for a push payload, containing a resource proactively sent // to us by another peer. type Push struct { @@ -71,6 +97,16 @@ type Push struct { Resource interface{} } +// NewPushMessage returns a new Message continaing a response with the given +// parameters. +func NewPushMessage(rt ResourceType, resource interface{}) *Message { + res := &Push{ + ResourceType: rt, + Resource: resource, + } + return New(MessageResponse, res) +} + // Write encodes and writes the Message into the given Writer. func (m *Message) Write(w io.Writer) error { return gob.NewEncoder(w).Encode(m) @@ -82,3 +118,13 @@ func Read(r io.Reader) (*Message, error) { err := gob.NewDecoder(r).Decode(&m) return &m, err } + +// RegisterGobTypes registers all the types used by gob in reading and writing +// messages. This should only be called once during initializaiton. +func RegisterGobTypes() { + dummy, _ := ma.NewMultiaddr("") + gob.Register(dummy) + gob.Register(Request{}) + gob.Register(Response{}) + gob.Register(Push{}) +} diff --git a/peer/peer.go b/peer/peer.go index b65aa99..cdf5f69 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -2,11 +2,13 @@ package peer import ( "context" + "encoding/json" "errors" "fmt" "time" log "github.com/Sirupsen/logrus" + "github.com/google/uuid" "github.com/libp2p/go-libp2p-crypto" "github.com/libp2p/go-libp2p-host" "github.com/libp2p/go-libp2p-net" @@ -15,6 +17,7 @@ import ( "github.com/libp2p/go-libp2p-swarm" bhost "github.com/libp2p/go-libp2p/p2p/host/basic" ma "github.com/multiformats/go-multiaddr" + protoerr "github.com/ubclaunchpad/cumulus/errors" "github.com/ubclaunchpad/cumulus/message" sn "github.com/ubclaunchpad/cumulus/subnet" ) @@ -31,6 +34,11 @@ const ( Timeout = time.Second * 30 ) +// MessageHandler is any package that implements HandleMessage +type MessageHandler interface { + HandleMessage(m message.Message, s net.Stream) +} + // Peer is a cumulus Peer composed of a host type Peer struct { host.Host @@ -99,13 +107,13 @@ func New(ip string, port int) (*Peer, error) { // This should be passed as the second argument to SetStreamHandler() after this // peer is initialized. func (p *Peer) Receive(s net.Stream) { - defer s.Close() - // Get remote peer's full multiaddress - remoteMA, err := makeMultiaddr( + remoteMA, err := NewMultiaddr( s.Conn().RemoteMultiaddr(), s.Conn().RemotePeer()) if err != nil { - log.Fatal("Failed to obtain valid remote peer multiaddress") + log.WithError(err).Error( + "Failed to obtain valid remote peer multiaddress") + return } // Add the remote peer to this peer's subnet @@ -113,26 +121,16 @@ func (p *Peer) Receive(s net.Stream) { if err != nil { // Subnet is full, advertise other available peers and then close // the stream - log.Debug("Peer subnet full. Advertising peers...") - p.advertisePeers(s) + log.WithError(err).Debug("Peer subnet full. Advertising peers...") + msg := message.NewResponseMessage("EFULL", + nil, p.subnet.StringMultiaddrs()) + msgErr := msg.Write(s) + if msgErr != nil { + log.WithError(err).Error("Failed to send ResourcePeerInfo") + } return } - defer p.subnet.RemovePeer(remoteMA) - - err = s.SetDeadline(time.Now().Add(Timeout)) - if err != nil { - log.WithError(err).Error("Failed to set read deadline on stream") - } - msg, err := message.Read(s) - if err != nil { - log.WithError(err).Error("Error reading from the stream") - return - } - - log.Debugf("Peer %s message:\n%s", p.ID(), msg.Type) - - // Respond to message - p.handleMessage(msg, s) + go p.Listen(remoteMA, s) } // Connect adds the given multiaddress to p's Peerstore and opens a stream @@ -148,16 +146,16 @@ func (p *Peer) Connect(peerma string) (net.Stream, error) { // Store the peer's address in this host's PeerStore p.Peerstore().AddAddr(peerid, targetAddr, pstore.PermanentAddrTTL) - log.Debug("Connected to Cumulus Peer:") - log.Debugf("Peer ID: %s", peerid.Pretty()) - log.Debug("Peer Address:", targetAddr) - // Open a stream with the peer stream, err := p.NewStream(context.Background(), peerid, CumulusProtocol) if err != nil { return nil, err } + err = stream.SetDeadline(time.Now().Add(Timeout)) + if err != nil { + log.WithError(err).Error("Failed to set read deadline on stream") + } mAddr, err := ma.NewMultiaddr(peerma) if err != nil { @@ -168,7 +166,6 @@ func (p *Peer) Connect(peerma string) (net.Stream, error) { err = p.subnet.AddPeer(mAddr, stream) if err != nil { stream.Close() - return nil, err } return stream, err @@ -179,6 +176,123 @@ func (p *Peer) Broadcast(m message.Message) error { return errors.New("Function not implemented") } +// Request sends a request to a remote peer over the given stream. +// Returns response if a response was received, otherwise returns error. +func (p *Peer) Request(req message.Request, s net.Stream) (*message.Response, error) { + reqMsg := message.New(message.MessageRequest, req) + err := reqMsg.Write(s) + if err != nil { + return nil, err + } + resMsg, err := message.Read(s) + if err != nil { + return nil, err + } + res := resMsg.Payload.(message.Response) + log.Debugf("Sending request with ResourceType: %d", req.ResourceType) + return &res, nil +} + +// Respond responds to a request from another peer +func (p *Peer) Respond(req message.Request, s net.Stream) { + var response message.Response + + switch req.ResourceType { + case message.ResourcePeerInfo: + response = message.Response{ + ID: req.ID, + Error: nil, + Resource: p.subnet.StringMultiaddrs(), + } + break + case message.ResourceBlock: + response = message.Response{ + ID: req.ID, + Error: protoerr.New(protoerr.NotImplemented), + Resource: nil, + } + break + case message.ResourceTransaction: + response = message.Response{ + ID: req.ID, + Error: protoerr.New(protoerr.NotImplemented), + Resource: nil, + } + break + default: + response = message.Response{ + ID: req.ID, + Error: protoerr.New(protoerr.InvalidResourceType), + Resource: nil, + } + } + + msg := message.New(message.MessageResponse, response) + err := msg.Write(s) + if err != nil { + log.WithError(err).Error("Failed to send reponse") + } else { + msgJSON, _ := json.Marshal(msg) + log.Info("Sending response: \n%s", string(msgJSON)) + } +} + +// NewMultiaddr creates a Multiaddress from the given Multiaddress (of the form +// /ip4//tcp/) and the peer id (a hash) and turn them +// into one Multiaddress. Will return error if Multiaddress is invalid. +func NewMultiaddr(iAddr ma.Multiaddr, pid lpeer.ID) (ma.Multiaddr, error) { + strAddr := iAddr.String() + strID := pid.Pretty() + strMA := fmt.Sprintf("%s/ipfs/%s", strAddr, strID) + mAddr, err := ma.NewMultiaddr(strMA) + return mAddr, err +} + +// HandleMessage responds to a received message +func (p *Peer) HandleMessage(m message.Message, s net.Stream) { + msgJSON, _ := json.Marshal(m) + log.Info("Received message: \n%s", string(msgJSON)) + + switch m.Type { + case message.MessageRequest: + p.Respond(m.Payload.(message.Request), s) + break + case message.MessageResponse: + log.Error("Message response handling not yet implemented") + break + case message.MessagePush: + log.Error("Message push handling not yet implemented") + break + default: + errRes := protoerr.New(protoerr.InvalidMessageType) + res := message.NewResponseMessage(uuid.New().String(), errRes, nil) + err := res.Write(s) + if err != nil { + log.WithError(err).Error("Failed to handle message") + } + } +} + +// Listen listens for messages over the stream and responds to them, closing +// the given stream and removing the remote peer from this peer's subnet when +// done. This should be run as a goroutine. +func (p *Peer) Listen(remoteMA ma.Multiaddr, s net.Stream) { + defer s.Close() + defer p.subnet.RemovePeer(remoteMA) + for s != nil { + err := s.SetDeadline(time.Now().Add(Timeout)) + if err != nil { + log.WithError(err).Error("Failed to set read deadline on stream") + } + msg, err := message.Read(s) + if err != nil { + log.WithError(err).Error("Error reading from the stream") + return + } + p.HandleMessage(*msg, s) + } +} + // ExtractPeerInfo extracts the peer ID and multiaddress from the // given multiaddress. // Returns peer ID (esentially 46 character hash created by the peer) @@ -212,35 +326,3 @@ func extractPeerInfo(peerma string) (lpeer.ID, ma.Multiaddr, error) { return peerid, trgtAddr, nil } - -// advertisePeers writes messages into the given stream advertising the -// multiaddress of each peer in this peer's subnet. -func (p *Peer) advertisePeers(s net.Stream) { - log.Debug("Peers on this subnet: ") - msg := message.New(message.MessageResponse, p.subnet.Multiaddrs()) - err := msg.Write(s) - if err != nil { - log.WithError(err).Error("Error writing PeerInfo message to stream") - } -} - -// makeMultiaddr creates a Multiaddress from the given Multiaddress (of the form -// /ip4//tcp/) and the peer id (a hash) and turn them -// into one Multiaddress. Will return error if Multiaddress is invalid. -func makeMultiaddr(iAddr ma.Multiaddr, pid lpeer.ID) (ma.Multiaddr, error) { - strAddr := iAddr.String() - strID := pid.Pretty() - strMA := fmt.Sprintf("%s/ipfs/%s", strAddr, strID) - mAddr, err := ma.NewMultiaddr(strMA) - return mAddr, err -} - -func (p *Peer) handleMessage(m *message.Message, s net.Stream) { - switch m.Type { - case message.MessageRequest: - p.advertisePeers(s) - break - default: - // Do nothing. WHEOOO! - } -} diff --git a/subnet/subnet.go b/subnet/subnet.go index 30e5b0f..1c4b750 100644 --- a/subnet/subnet.go +++ b/subnet/subnet.go @@ -17,14 +17,13 @@ const ( // Subnet represents the set of peers a peer is connected to at any given time type Subnet struct { peers map[ma.Multiaddr]net.Stream - maxPeers uint16 - numPeers uint16 + maxPeers int } // New returns a pointer to an empty Subnet with the given maxPeers. -func New(maxPeers uint16) *Subnet { +func New(maxPeers int) *Subnet { p := make(map[ma.Multiaddr]net.Stream) - sn := Subnet{maxPeers: maxPeers, numPeers: 0, peers: p} + sn := Subnet{maxPeers: maxPeers, peers: p} return &sn } @@ -32,7 +31,7 @@ func New(maxPeers uint16) *Subnet { // local peer and the remote peer to the subnet. If the subnet is already // full, or the multiaddress is not valid, returns error. func (sn *Subnet) AddPeer(mAddr ma.Multiaddr, stream net.Stream) error { - if sn.isFull() { + if sn.Full() { return errors.New("Cannot insert new mapping, Subnet is already full") } @@ -48,7 +47,6 @@ func (sn *Subnet) AddPeer(mAddr ma.Multiaddr, stream net.Stream) error { } else { log.Debugf("Adding peer %s to subnet", mAddr.String()) sn.peers[mAddr] = stream - sn.numPeers++ } return nil } @@ -58,13 +56,12 @@ func (sn *Subnet) AddPeer(mAddr ma.Multiaddr, stream net.Stream) error { func (sn *Subnet) RemovePeer(mAddr ma.Multiaddr) { log.Debugf("Removing peer %s from subnet", mAddr.String()) delete(sn.peers, mAddr) - sn.numPeers-- } -// isFull returns true if the number of peers in the sunbet is at or over the +// Full returns true if the number of peers in the sunbet is at or over the // limit set for that subnet, otherwise returns false. -func (sn *Subnet) isFull() bool { - return sn.numPeers >= sn.maxPeers +func (sn *Subnet) Full() bool { + return len(sn.peers) >= sn.maxPeers } // Multiaddrs returns a list of all multiaddresses contined in this subnet @@ -75,3 +72,12 @@ func (sn *Subnet) Multiaddrs() []ma.Multiaddr { } return mAddrs } + +// StringMultiaddrs returns a list of all multiaddrs in this subnet as strings +func (sn *Subnet) StringMultiaddrs() []string { + mAddrs := make([]string, 0, len(sn.peers)) + for mAddr := range sn.peers { + mAddrs = append(mAddrs, mAddr.String()) + } + return mAddrs +} From ac7570953e9de943a287aa98c2ecde8d3ee5b5cd Mon Sep 17 00:00:00 2001 From: Bruno Bachmann Date: Tue, 23 May 2017 23:07:11 -0700 Subject: [PATCH 12/37] Add protocol errors, update message, peer, and subnet --- errors/error.go | 65 +++++++++++++++ main.go | 23 +++--- message/message.go | 46 +++++++++++ peer/peer.go | 200 ++++++++++++++++++++++++++++++++------------- subnet/subnet.go | 26 +++--- 5 files changed, 280 insertions(+), 80 deletions(-) create mode 100644 errors/error.go diff --git a/errors/error.go b/errors/error.go new file mode 100644 index 0000000..fc3688c --- /dev/null +++ b/errors/error.go @@ -0,0 +1,65 @@ +package errors + +import "fmt" + +const ( + // InvalidMessageType occurs when a message is received with an unknown + // MesssageType value. + InvalidMessageType = 401 + // InvalidResourceType occurs when a request is received with an unknown + // ResourceType value. + InvalidResourceType = 402 + // SubnetFull occurs when a stream is opened with a peer who's Subnet is + // already full. + SubnetFull = 501 + // NotImplemented occurs when a message or request is received whos response + // requires functionality that does not yet exist. + NotImplemented = 502 +) + +const ( + invalidMessageTypeMsg = "Invalid message type" + invalidResourceTypeMsg = "Invalid resource type" + subnetFullMsg = "Failed to add peer to subnet (peer subnet full)" + notImplementedMsg = "Functionality not yet implemented" +) + +// ProtocolError is a container for error messages returned in Peer +// Response bodies. +type ProtocolError struct { + Code int + Message string +} + +// New returns new ProtocolError with the given parameters. +// code argument should be one of the error codes defined above. +func New(code int) *ProtocolError { + var msg string + + switch code { + case SubnetFull: + msg = subnetFullMsg + break + case InvalidMessageType: + msg = invalidMessageTypeMsg + break + case InvalidResourceType: + msg = invalidResourceTypeMsg + break + case NotImplemented: + msg = notImplementedMsg + break + default: + panic("Attempt to create error with invalid error code") + } + + return &ProtocolError{ + Code: code, + Message: msg, + } +} + +// Error returns the string representation of the given ProtocolError +func (e ProtocolError) Error() string { + return fmt.Sprintf("%d: %s", e.Code, e.Message) +} diff --git a/main.go b/main.go index 44b7ecc..0892da8 100644 --- a/main.go +++ b/main.go @@ -2,14 +2,16 @@ package main import ( "flag" - "io/ioutil" log "github.com/Sirupsen/logrus" + "github.com/google/uuid" + "github.com/ubclaunchpad/cumulus/message" "github.com/ubclaunchpad/cumulus/peer" ) func main() { log.Info("Starting Cumulus Peer") + message.RegisterGobTypes() // Get and parse command line arguments // targetPeer is a Multiaddr representing the target peer to connect to @@ -46,19 +48,18 @@ func main() { log.Fatal(err) } - // Send a message to the peer - _, err = stream.Write([]byte("Hello, world!\n")) - if err != nil { - log.Fatal(err) + // Request peer info + request := message.Request{ + ID: uuid.New().String(), + ResourceType: message.ResourcePeerInfo, + Params: nil, } - - // Read the reply from the peer - reply, err := ioutil.ReadAll(stream) + response, err := host.Request(request, stream) if err != nil { - log.Fatal(err) + log.WithError(err).Error("Error writing message to stream") + return } - log.Debugf("Peer %s read reply: %s", host.ID(), string(reply)) - + host.HandleMessage(*message.NewResponseMessage("", nil, response), stream) host.Close() } diff --git a/message/message.go b/message/message.go index f2fd34a..69cca67 100644 --- a/message/message.go +++ b/message/message.go @@ -3,6 +3,9 @@ package message import ( "encoding/gob" "io" + + "github.com/google/uuid" + ma "github.com/multiformats/go-multiaddr" ) type ( @@ -55,6 +58,17 @@ type Request struct { Params map[string]interface{} } +// NewRequestMessage returns a new Message continaing a request with the given +// parameters. +func NewRequestMessage(rt ResourceType, p map[string]interface{}) *Message { + req := Request{ + ID: uuid.New().String(), + ResourceType: rt, + Params: p, + } + return New(MessageRequest, req) +} + // Response is a container for a response payload, containing the unique request // ID of the request prompting it, an Error (if one occurred), and the requested // resource (if no error occurred). @@ -64,6 +78,18 @@ type Response struct { Resource interface{} } +// NewResponseMessage returns a new Message continaing a response with the given +// parameters. ResponseMessages should have the same ID as that of the request +// message they are a response to. +func NewResponseMessage(id string, err error, resource interface{}) *Message { + res := &Response{ + ID: id, + Error: err, + Resource: resource, + } + return New(MessageResponse, res) +} + // Push is a container for a push payload, containing a resource proactively sent // to us by another peer. type Push struct { @@ -71,6 +97,16 @@ type Push struct { Resource interface{} } +// NewPushMessage returns a new Message continaing a response with the given +// parameters. +func NewPushMessage(rt ResourceType, resource interface{}) *Message { + res := &Push{ + ResourceType: rt, + Resource: resource, + } + return New(MessageResponse, res) +} + // Write encodes and writes the Message into the given Writer. func (m *Message) Write(w io.Writer) error { return gob.NewEncoder(w).Encode(m) @@ -82,3 +118,13 @@ func Read(r io.Reader) (*Message, error) { err := gob.NewDecoder(r).Decode(&m) return &m, err } + +// RegisterGobTypes registers all the types used by gob in reading and writing +// messages. This should only be called once during initializaiton. +func RegisterGobTypes() { + dummy, _ := ma.NewMultiaddr("") + gob.Register(dummy) + gob.Register(Request{}) + gob.Register(Response{}) + gob.Register(Push{}) +} diff --git a/peer/peer.go b/peer/peer.go index b65aa99..8b4b4a4 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -2,11 +2,13 @@ package peer import ( "context" + "encoding/json" "errors" "fmt" "time" log "github.com/Sirupsen/logrus" + "github.com/google/uuid" "github.com/libp2p/go-libp2p-crypto" "github.com/libp2p/go-libp2p-host" "github.com/libp2p/go-libp2p-net" @@ -15,6 +17,7 @@ import ( "github.com/libp2p/go-libp2p-swarm" bhost "github.com/libp2p/go-libp2p/p2p/host/basic" ma "github.com/multiformats/go-multiaddr" + protoerr "github.com/ubclaunchpad/cumulus/errors" "github.com/ubclaunchpad/cumulus/message" sn "github.com/ubclaunchpad/cumulus/subnet" ) @@ -31,6 +34,11 @@ const ( Timeout = time.Second * 30 ) +// MessageHandler is any package that implements HandleMessage +type MessageHandler interface { + HandleMessage(m message.Message, s net.Stream) +} + // Peer is a cumulus Peer composed of a host type Peer struct { host.Host @@ -99,13 +107,13 @@ func New(ip string, port int) (*Peer, error) { // This should be passed as the second argument to SetStreamHandler() after this // peer is initialized. func (p *Peer) Receive(s net.Stream) { - defer s.Close() - // Get remote peer's full multiaddress - remoteMA, err := makeMultiaddr( + remoteMA, err := NewMultiaddr( s.Conn().RemoteMultiaddr(), s.Conn().RemotePeer()) if err != nil { - log.Fatal("Failed to obtain valid remote peer multiaddress") + log.WithError(err).Error( + "Failed to obtain valid remote peer multiaddress") + return } // Add the remote peer to this peer's subnet @@ -113,26 +121,16 @@ func (p *Peer) Receive(s net.Stream) { if err != nil { // Subnet is full, advertise other available peers and then close // the stream - log.Debug("Peer subnet full. Advertising peers...") - p.advertisePeers(s) + log.WithError(err).Debug("Peer subnet full. Advertising peers...") + msg := message.NewResponseMessage(uuid.New().String(), + nil, p.subnet.StringMultiaddrs()) + msgErr := msg.Write(s) + if msgErr != nil { + log.WithError(err).Error("Failed to send ResourcePeerInfo") + } return } - defer p.subnet.RemovePeer(remoteMA) - - err = s.SetDeadline(time.Now().Add(Timeout)) - if err != nil { - log.WithError(err).Error("Failed to set read deadline on stream") - } - msg, err := message.Read(s) - if err != nil { - log.WithError(err).Error("Error reading from the stream") - return - } - - log.Debugf("Peer %s message:\n%s", p.ID(), msg.Type) - - // Respond to message - p.handleMessage(msg, s) + go p.Listen(remoteMA, s) } // Connect adds the given multiaddress to p's Peerstore and opens a stream @@ -148,16 +146,16 @@ func (p *Peer) Connect(peerma string) (net.Stream, error) { // Store the peer's address in this host's PeerStore p.Peerstore().AddAddr(peerid, targetAddr, pstore.PermanentAddrTTL) - log.Debug("Connected to Cumulus Peer:") - log.Debugf("Peer ID: %s", peerid.Pretty()) - log.Debug("Peer Address:", targetAddr) - // Open a stream with the peer stream, err := p.NewStream(context.Background(), peerid, CumulusProtocol) if err != nil { return nil, err } + err = stream.SetDeadline(time.Now().Add(Timeout)) + if err != nil { + log.WithError(err).Error("Failed to set read deadline on stream") + } mAddr, err := ma.NewMultiaddr(peerma) if err != nil { @@ -168,7 +166,6 @@ func (p *Peer) Connect(peerma string) (net.Stream, error) { err = p.subnet.AddPeer(mAddr, stream) if err != nil { stream.Close() - return nil, err } return stream, err @@ -179,6 +176,123 @@ func (p *Peer) Broadcast(m message.Message) error { return errors.New("Function not implemented") } +// Request sends a request to a remote peer over the given stream. +// Returns response if a response was received, otherwise returns error. +func (p *Peer) Request(req message.Request, s net.Stream) (*message.Response, error) { + reqMsg := message.New(message.MessageRequest, req) + err := reqMsg.Write(s) + if err != nil { + return nil, err + } + resMsg, err := message.Read(s) + if err != nil { + return nil, err + } + res := resMsg.Payload.(message.Response) + log.Debugf("Sending request with ResourceType: %d", req.ResourceType) + return &res, nil +} + +// Respond responds to a request from another peer +func (p *Peer) Respond(req message.Request, s net.Stream) { + var response message.Response + + switch req.ResourceType { + case message.ResourcePeerInfo: + response = message.Response{ + ID: req.ID, + Error: nil, + Resource: p.subnet.StringMultiaddrs(), + } + break + case message.ResourceBlock: + response = message.Response{ + ID: req.ID, + Error: protoerr.New(protoerr.NotImplemented), + Resource: nil, + } + break + case message.ResourceTransaction: + response = message.Response{ + ID: req.ID, + Error: protoerr.New(protoerr.NotImplemented), + Resource: nil, + } + break + default: + response = message.Response{ + ID: req.ID, + Error: protoerr.New(protoerr.InvalidResourceType), + Resource: nil, + } + } + + msg := message.New(message.MessageResponse, response) + err := msg.Write(s) + if err != nil { + log.WithError(err).Error("Failed to send reponse") + } else { + msgJSON, _ := json.Marshal(msg) + log.Info("Sending response: \n%s", string(msgJSON)) + } +} + +// NewMultiaddr creates a Multiaddress from the given Multiaddress (of the form +// /ip4//tcp/) and the peer id (a hash) and turn them +// into one Multiaddress. Will return error if Multiaddress is invalid. +func NewMultiaddr(iAddr ma.Multiaddr, pid lpeer.ID) (ma.Multiaddr, error) { + strAddr := iAddr.String() + strID := pid.Pretty() + strMA := fmt.Sprintf("%s/ipfs/%s", strAddr, strID) + mAddr, err := ma.NewMultiaddr(strMA) + return mAddr, err +} + +// HandleMessage responds to a received message +func (p *Peer) HandleMessage(m message.Message, s net.Stream) { + msgJSON, _ := json.Marshal(m) + log.Info("Received message: \n%s", string(msgJSON)) + + switch m.Type { + case message.MessageRequest: + p.Respond(m.Payload.(message.Request), s) + break + case message.MessageResponse: + log.Error("Message response handling not yet implemented") + break + case message.MessagePush: + log.Error("Message push handling not yet implemented") + break + default: + errRes := protoerr.New(protoerr.InvalidMessageType) + res := message.NewResponseMessage(uuid.New().String(), errRes, nil) + err := res.Write(s) + if err != nil { + log.WithError(err).Error("Failed to handle message") + } + } +} + +// Listen listens for messages over the stream and responds to them, closing +// the given stream and removing the remote peer from this peer's subnet when +// done. This should be run as a goroutine. +func (p *Peer) Listen(remoteMA ma.Multiaddr, s net.Stream) { + defer s.Close() + defer p.subnet.RemovePeer(remoteMA) + for s != nil { + err := s.SetDeadline(time.Now().Add(Timeout)) + if err != nil { + log.WithError(err).Error("Failed to set read deadline on stream") + } + msg, err := message.Read(s) + if err != nil { + log.WithError(err).Error("Error reading from the stream") + return + } + p.HandleMessage(*msg, s) + } +} + // ExtractPeerInfo extracts the peer ID and multiaddress from the // given multiaddress. // Returns peer ID (esentially 46 character hash created by the peer) @@ -212,35 +326,3 @@ func extractPeerInfo(peerma string) (lpeer.ID, ma.Multiaddr, error) { return peerid, trgtAddr, nil } - -// advertisePeers writes messages into the given stream advertising the -// multiaddress of each peer in this peer's subnet. -func (p *Peer) advertisePeers(s net.Stream) { - log.Debug("Peers on this subnet: ") - msg := message.New(message.MessageResponse, p.subnet.Multiaddrs()) - err := msg.Write(s) - if err != nil { - log.WithError(err).Error("Error writing PeerInfo message to stream") - } -} - -// makeMultiaddr creates a Multiaddress from the given Multiaddress (of the form -// /ip4//tcp/) and the peer id (a hash) and turn them -// into one Multiaddress. Will return error if Multiaddress is invalid. -func makeMultiaddr(iAddr ma.Multiaddr, pid lpeer.ID) (ma.Multiaddr, error) { - strAddr := iAddr.String() - strID := pid.Pretty() - strMA := fmt.Sprintf("%s/ipfs/%s", strAddr, strID) - mAddr, err := ma.NewMultiaddr(strMA) - return mAddr, err -} - -func (p *Peer) handleMessage(m *message.Message, s net.Stream) { - switch m.Type { - case message.MessageRequest: - p.advertisePeers(s) - break - default: - // Do nothing. WHEOOO! - } -} diff --git a/subnet/subnet.go b/subnet/subnet.go index 30e5b0f..1c4b750 100644 --- a/subnet/subnet.go +++ b/subnet/subnet.go @@ -17,14 +17,13 @@ const ( // Subnet represents the set of peers a peer is connected to at any given time type Subnet struct { peers map[ma.Multiaddr]net.Stream - maxPeers uint16 - numPeers uint16 + maxPeers int } // New returns a pointer to an empty Subnet with the given maxPeers. -func New(maxPeers uint16) *Subnet { +func New(maxPeers int) *Subnet { p := make(map[ma.Multiaddr]net.Stream) - sn := Subnet{maxPeers: maxPeers, numPeers: 0, peers: p} + sn := Subnet{maxPeers: maxPeers, peers: p} return &sn } @@ -32,7 +31,7 @@ func New(maxPeers uint16) *Subnet { // local peer and the remote peer to the subnet. If the subnet is already // full, or the multiaddress is not valid, returns error. func (sn *Subnet) AddPeer(mAddr ma.Multiaddr, stream net.Stream) error { - if sn.isFull() { + if sn.Full() { return errors.New("Cannot insert new mapping, Subnet is already full") } @@ -48,7 +47,6 @@ func (sn *Subnet) AddPeer(mAddr ma.Multiaddr, stream net.Stream) error { } else { log.Debugf("Adding peer %s to subnet", mAddr.String()) sn.peers[mAddr] = stream - sn.numPeers++ } return nil } @@ -58,13 +56,12 @@ func (sn *Subnet) AddPeer(mAddr ma.Multiaddr, stream net.Stream) error { func (sn *Subnet) RemovePeer(mAddr ma.Multiaddr) { log.Debugf("Removing peer %s from subnet", mAddr.String()) delete(sn.peers, mAddr) - sn.numPeers-- } -// isFull returns true if the number of peers in the sunbet is at or over the +// Full returns true if the number of peers in the sunbet is at or over the // limit set for that subnet, otherwise returns false. -func (sn *Subnet) isFull() bool { - return sn.numPeers >= sn.maxPeers +func (sn *Subnet) Full() bool { + return len(sn.peers) >= sn.maxPeers } // Multiaddrs returns a list of all multiaddresses contined in this subnet @@ -75,3 +72,12 @@ func (sn *Subnet) Multiaddrs() []ma.Multiaddr { } return mAddrs } + +// StringMultiaddrs returns a list of all multiaddrs in this subnet as strings +func (sn *Subnet) StringMultiaddrs() []string { + mAddrs := make([]string, 0, len(sn.peers)) + for mAddr := range sn.peers { + mAddrs = append(mAddrs, mAddr.String()) + } + return mAddrs +} From 1756a51757bad7e5a1fe862170cf5abb690cfa67 Mon Sep 17 00:00:00 2001 From: Bruno Bachmann Date: Thu, 25 May 2017 23:14:26 -0700 Subject: [PATCH 13/37] Add support for message handling among goroutines --- errors/error.go | 9 +---- peer/peer.go | 102 +++++++++++++++++++++++++----------------------- 2 files changed, 55 insertions(+), 56 deletions(-) diff --git a/errors/error.go b/errors/error.go index fc3688c..3d0f9e2 100644 --- a/errors/error.go +++ b/errors/error.go @@ -3,12 +3,9 @@ package errors import "fmt" const ( - // InvalidMessageType occurs when a message is received with an unknown - // MesssageType value. - InvalidMessageType = 401 // InvalidResourceType occurs when a request is received with an unknown // ResourceType value. - InvalidResourceType = 402 + InvalidResourceType = 401 // SubnetFull occurs when a stream is opened with a peer who's Subnet is // already full. SubnetFull = 501 @@ -18,7 +15,6 @@ const ( ) const ( - invalidMessageTypeMsg = "Invalid message type" invalidResourceTypeMsg = "Invalid resource type" subnetFullMsg = "Failed to add peer to subnet (peer subnet full)" notImplementedMsg = "Functionality not yet implemented" @@ -40,9 +36,6 @@ func New(code int) *ProtocolError { case SubnetFull: msg = subnetFullMsg break - case InvalidMessageType: - msg = invalidMessageTypeMsg - break case InvalidResourceType: msg = invalidResourceTypeMsg break diff --git a/peer/peer.go b/peer/peer.go index 8b4b4a4..b3cbc81 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -5,10 +5,10 @@ import ( "encoding/json" "errors" "fmt" + "sync" "time" log "github.com/Sirupsen/logrus" - "github.com/google/uuid" "github.com/libp2p/go-libp2p-crypto" "github.com/libp2p/go-libp2p-host" "github.com/libp2p/go-libp2p-net" @@ -34,15 +34,12 @@ const ( Timeout = time.Second * 30 ) -// MessageHandler is any package that implements HandleMessage -type MessageHandler interface { - HandleMessage(m message.Message, s net.Stream) -} - // Peer is a cumulus Peer composed of a host type Peer struct { host.Host - subnet sn.Subnet + subnet sn.Subnet + listeners map[string]chan message.Response + listenersLock *sync.RWMutex } // New creates a Cumulus host with the given IP addr and TCP port. @@ -85,7 +82,12 @@ func New(ip string, port int) (*Peer, error) { // Actually create the host and peer with the network we just set up. host := bhost.New(netwrk) - peer := &Peer{Host: host, subnet: subnet} + peer := &Peer{ + Host: host, + subnet: subnet, + listeners: make(map[string]chan message.Response), + listenersLock: &sync.RWMutex{}, + } // Build host multiaddress hostAddr, _ := ma.NewMultiaddr(fmt.Sprintf("/ipfs/%s", @@ -98,7 +100,6 @@ func New(ip string, port int) (*Peer, error) { // Add this host's address to its peerstore (avoid's net/identi error) ps.AddAddr(pid, fullAddr, pstore.PermanentAddrTTL) - return peer, nil } @@ -122,12 +123,12 @@ func (p *Peer) Receive(s net.Stream) { // Subnet is full, advertise other available peers and then close // the stream log.WithError(err).Debug("Peer subnet full. Advertising peers...") - msg := message.NewResponseMessage(uuid.New().String(), - nil, p.subnet.StringMultiaddrs()) + msg := message.NewPushMessage(message.ResourcePeerInfo, p.subnet.StringMultiaddrs()) msgErr := msg.Write(s) if msgErr != nil { log.WithError(err).Error("Failed to send ResourcePeerInfo") } + s.Close() return } go p.Listen(remoteMA, s) @@ -147,8 +148,7 @@ func (p *Peer) Connect(peerma string) (net.Stream, error) { p.Peerstore().AddAddr(peerid, targetAddr, pstore.PermanentAddrTTL) // Open a stream with the peer - stream, err := p.NewStream(context.Background(), peerid, - CumulusProtocol) + stream, err := p.NewStream(context.Background(), peerid, CumulusProtocol) if err != nil { return nil, err } @@ -178,53 +178,44 @@ func (p *Peer) Broadcast(m message.Message) error { // Request sends a request to a remote peer over the given stream. // Returns response if a response was received, otherwise returns error. +// You should typically run this function in a goroutine func (p *Peer) Request(req message.Request, s net.Stream) (*message.Response, error) { + // Set up a listen channel to listen for the response (remove when done) + lchan := p.newListener(req.ID) + defer p.removeListener(req.ID) + + // Send request reqMsg := message.New(message.MessageRequest, req) err := reqMsg.Write(s) if err != nil { return nil, err } - resMsg, err := message.Read(s) - if err != nil { - return nil, err + + // Receive response or timeout + select { + case res := <-lchan: + return &res, nil + case <-time.After(Timeout): + return nil, errors.New("Timed out waiting for response") } - res := resMsg.Payload.(message.Response) - log.Debugf("Sending request with ResourceType: %d", req.ResourceType) - return &res, nil } // Respond responds to a request from another peer func (p *Peer) Respond(req message.Request, s net.Stream) { - var response message.Response + response := message.Response{ID: req.ID, Error: nil, Resource: nil} switch req.ResourceType { case message.ResourcePeerInfo: - response = message.Response{ - ID: req.ID, - Error: nil, - Resource: p.subnet.StringMultiaddrs(), - } + response.Resource = p.subnet.StringMultiaddrs() break case message.ResourceBlock: - response = message.Response{ - ID: req.ID, - Error: protoerr.New(protoerr.NotImplemented), - Resource: nil, - } + response.Error = protoerr.New(protoerr.NotImplemented) break case message.ResourceTransaction: - response = message.Response{ - ID: req.ID, - Error: protoerr.New(protoerr.NotImplemented), - Resource: nil, - } + response.Error = protoerr.New(protoerr.NotImplemented) break default: - response = message.Response{ - ID: req.ID, - Error: protoerr.New(protoerr.InvalidResourceType), - Resource: nil, - } + response.Error = protoerr.New(protoerr.InvalidResourceType) } msg := message.New(message.MessageResponse, response) @@ -255,21 +246,22 @@ func (p *Peer) HandleMessage(m message.Message, s net.Stream) { switch m.Type { case message.MessageRequest: + // Respond to the request by sending request resource p.Respond(m.Payload.(message.Request), s) break case message.MessageResponse: - log.Error("Message response handling not yet implemented") + // Pass the response to the goroutine that requested it + lchan := p.listeners[m.Payload.(message.Response).ID] + if lchan != nil { + lchan <- m.Payload.(message.Response) + } break case message.MessagePush: + // Handle data from push message log.Error("Message push handling not yet implemented") break default: - errRes := protoerr.New(protoerr.InvalidMessageType) - res := message.NewResponseMessage(uuid.New().String(), errRes, nil) - err := res.Write(s) - if err != nil { - log.WithError(err).Error("Failed to handle message") - } + // Invalid message type, ignore } } @@ -323,6 +315,20 @@ func extractPeerInfo(peerma string) (lpeer.ID, ma.Multiaddr, error) { } trgtAddr := ipfsaddr.Decapsulate(targetPeerAddr) - return peerid, trgtAddr, nil } + +// newListener synchronously adds a listener to this peer's listeners map +func (p *Peer) newListener(id string) chan message.Response { + p.listenersLock.Lock() + p.listeners[id] = make(chan message.Response) + p.listenersLock.Unlock() + return p.listeners[id] +} + +// removeListener synchronously removes a listener from this peer's listeners map +func (p *Peer) removeListener(id string) { + p.listenersLock.Lock() + delete(p.listeners, id) + p.listenersLock.Unlock() +} From d8508fd826c26fe8d18ca432eba9ef5ee98820f6 Mon Sep 17 00:00:00 2001 From: Bruno Bachmann Date: Sat, 27 May 2017 12:57:19 -0700 Subject: [PATCH 14/37] Update peers tp handle asynch messsaging, add tests --- main.go | 2 -- message/message.go | 2 +- peer/peer.go | 29 +++++++++++++---- peer/peer_test.go | 81 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 10 deletions(-) diff --git a/main.go b/main.go index 0892da8..83a0239 100644 --- a/main.go +++ b/main.go @@ -11,7 +11,6 @@ import ( func main() { log.Info("Starting Cumulus Peer") - message.RegisterGobTypes() // Get and parse command line arguments // targetPeer is a Multiaddr representing the target peer to connect to @@ -52,7 +51,6 @@ func main() { request := message.Request{ ID: uuid.New().String(), ResourceType: message.ResourcePeerInfo, - Params: nil, } response, err := host.Request(request, stream) if err != nil { diff --git a/message/message.go b/message/message.go index 69cca67..046291a 100644 --- a/message/message.go +++ b/message/message.go @@ -121,7 +121,7 @@ func Read(r io.Reader) (*Message, error) { // RegisterGobTypes registers all the types used by gob in reading and writing // messages. This should only be called once during initializaiton. -func RegisterGobTypes() { +func init() { dummy, _ := ma.NewMultiaddr("") gob.Register(dummy) gob.Register(Request{}) diff --git a/peer/peer.go b/peer/peer.go index b3cbc81..1b37c8c 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -168,7 +168,8 @@ func (p *Peer) Connect(peerma string) (net.Stream, error) { stream.Close() } - return stream, err + go p.Listen(mAddr, stream) + return stream, nil } // Broadcast sends message to all peers this peer is currently connected to @@ -241,8 +242,10 @@ func NewMultiaddr(iAddr ma.Multiaddr, pid lpeer.ID) (ma.Multiaddr, error) { // HandleMessage responds to a received message func (p *Peer) HandleMessage(m message.Message, s net.Stream) { - msgJSON, _ := json.Marshal(m) - log.Info("Received message: \n%s", string(msgJSON)) + msgJSON, err := json.Marshal(m) + if err == nil { + log.Info("Received message: \n%s", string(msgJSON)) + } switch m.Type { case message.MessageRequest: @@ -251,9 +254,11 @@ func (p *Peer) HandleMessage(m message.Message, s net.Stream) { break case message.MessageResponse: // Pass the response to the goroutine that requested it - lchan := p.listeners[m.Payload.(message.Response).ID] + res := m.Payload.(message.Response) + lchan := p.getListener(res.ID) if lchan != nil { - lchan <- m.Payload.(message.Response) + log.Debug("Found listener channel for response") + lchan <- res } break case message.MessagePush: @@ -262,6 +267,7 @@ func (p *Peer) HandleMessage(m message.Message, s net.Stream) { break default: // Invalid message type, ignore + log.Errorln("Received message with invalid type") } } @@ -271,17 +277,19 @@ func (p *Peer) HandleMessage(m message.Message, s net.Stream) { func (p *Peer) Listen(remoteMA ma.Multiaddr, s net.Stream) { defer s.Close() defer p.subnet.RemovePeer(remoteMA) - for s != nil { + for { err := s.SetDeadline(time.Now().Add(Timeout)) if err != nil { log.WithError(err).Error("Failed to set read deadline on stream") + return } msg, err := message.Read(s) if err != nil { log.WithError(err).Error("Error reading from the stream") return } - p.HandleMessage(*msg, s) + log.Debug("Listener received message") + go p.HandleMessage(*msg, s) } } @@ -332,3 +340,10 @@ func (p *Peer) removeListener(id string) { delete(p.listeners, id) p.listenersLock.Unlock() } + +func (p *Peer) getListener(id string) chan message.Response { + p.listenersLock.RLock() + lchan := p.listeners[id] + p.listenersLock.RUnlock() + return lchan +} diff --git a/peer/peer_test.go b/peer/peer_test.go index 4d0f4e3..5b2d7e3 100644 --- a/peer/peer_test.go +++ b/peer/peer_test.go @@ -5,6 +5,9 @@ import ( "testing" log "github.com/Sirupsen/logrus" + "github.com/google/uuid" + "github.com/ubclaunchpad/cumulus/message" + sn "github.com/ubclaunchpad/cumulus/subnet" ) func TestMain(t *testing.T) { @@ -129,3 +132,81 @@ func TestReceiveInvalidAddress(t *testing.T) { t.Fail() } } + +func TestSubnetFull(t *testing.T) { + testPeer, err := New("127.0.0.1", 8080) + if err != nil { + t.Fail() + } + testPeer.SetStreamHandler(CumulusProtocol, testPeer.Receive) + peers := make([]*Peer, sn.DefaultMaxPeers) + + for i := 1; i < sn.DefaultMaxPeers; i++ { + peers[i], err = New("127.0.0.1", 8080+i) + if err != nil { + fmt.Println("Failed trying to create a new test peer") + t.Fail() + } + peers[i].SetStreamHandler(CumulusProtocol, peers[i].Receive) + ma, maErr := NewMultiaddr(peers[i].Addrs()[0], peers[i].ID()) + if maErr != nil { + t.Fail() + } + _, err = testPeer.Connect(ma.String()) + if err != nil { + fmt.Println("Failed trying to connect to a test peer") + fmt.Println(ma.String()) + t.Fail() + } + } + + lastPeer, err := New("127.0.0.1", 8081+sn.DefaultMaxPeers) + if err != nil { + fmt.Println("Failed trying to create the last test peer") + t.Fail() + } + _, err = testPeer.Connect(lastPeer.Addrs()[0].String()) + if err == nil { + fmt.Println("Failed trying to connect to the last test peer") + t.Fail() + } +} + +func TestRequest(t *testing.T) { + requester, err := New(DefaultIP, DefaultPort) + if err != nil { + t.Fail() + } + + responder, err := New(DefaultIP, 8080) + if err != nil { + t.Fail() + } + + requester.SetStreamHandler(CumulusProtocol, requester.Receive) + responder.SetStreamHandler(CumulusProtocol, responder.Receive) + responderAddr, err := NewMultiaddr(responder.Addrs()[0], responder.ID()) + if err != nil { + t.Fail() + } + + stream, err := requester.Connect(responderAddr.String()) + if err != nil { + fmt.Println("Failed to connect to remote peer") + t.Fail() + } + + request := message.Request{ + ID: uuid.New().String(), + ResourceType: message.ResourcePeerInfo, + Params: nil, + } + response, err := requester.Request(request, stream) + if err != nil { + fmt.Printf("Failed to make request: %s", err) + t.Fail() + } else if response.Error != nil { + fmt.Printf("Remote peer returned response %d", response.Error) + t.Fail() + } +} From cadcb35d8e8f5fa9e53c3d0677459a5d8b6f5d4b Mon Sep 17 00:00:00 2001 From: Bruno Bachmann Date: Sat, 27 May 2017 15:55:33 -0700 Subject: [PATCH 15/37] Refactor Peer, fix Subnet bug, add simple Broadcast() to peer --- errors/error.go | 18 +------ glide.lock | 6 ++- glide.yaml | 2 + main.go | 4 +- message/message.go | 5 +- peer/peer.go | 114 ++++++++++++++++++++++++--------------------- subnet/subnet.go | 68 ++++++++++++++------------- 7 files changed, 107 insertions(+), 110 deletions(-) diff --git a/errors/error.go b/errors/error.go index 3d0f9e2..0c35e9c 100644 --- a/errors/error.go +++ b/errors/error.go @@ -29,23 +29,7 @@ type ProtocolError struct { // New returns new ProtocolError with the given parameters. // code argument should be one of the error codes defined above. -func New(code int) *ProtocolError { - var msg string - - switch code { - case SubnetFull: - msg = subnetFullMsg - break - case InvalidResourceType: - msg = invalidResourceTypeMsg - break - case NotImplemented: - msg = notImplementedMsg - break - default: - panic("Attempt to create error with invalid error code") - } - +func New(code int, msg string) *ProtocolError { return &ProtocolError{ Code: code, Message: msg, diff --git a/glide.lock b/glide.lock index 9522746..31f73e5 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 474d608bbb499683dec7db95f6a10f5df61bd5d447651e04567c0d10b816cfe3 -updated: 2017-05-13T15:17:47.793598435-07:00 +hash: 1b81695a566f49c0195d3c273cd75f43c864147343e6beac071fc67daf29b6d5 +updated: 2017-05-27T15:51:45.462122864-07:00 imports: - name: github.com/agl/ed25519 version: 5312a61534124124185d41f09206b9fef1d88403 @@ -25,6 +25,8 @@ imports: subpackages: - proto - io +- name: github.com/google/uuid + version: 064e2069ce9c359c118179501254f67d7d37ba24 - name: github.com/gxed/eventfd version: 80a92cca79a8041496ccc9dd773fcb52a57ec6f9 - name: github.com/gxed/GoEndian diff --git a/glide.yaml b/glide.yaml index 6b0b13f..8868470 100644 --- a/glide.yaml +++ b/glide.yaml @@ -11,3 +11,5 @@ import: - p2p/host/basic - package: github.com/multiformats/go-multiaddr - package: github.com/Sirupsen/logrus +- package: github.com/google/uuid + version: ~0.2.0 diff --git a/main.go b/main.go index 83a0239..7dddee7 100644 --- a/main.go +++ b/main.go @@ -52,12 +52,10 @@ func main() { ID: uuid.New().String(), ResourceType: message.ResourcePeerInfo, } - response, err := host.Request(request, stream) + _, err = host.Request(request, stream) if err != nil { log.WithError(err).Error("Error writing message to stream") return } - - host.HandleMessage(*message.NewResponseMessage("", nil, response), stream) host.Close() } diff --git a/message/message.go b/message/message.go index 046291a..38c7d18 100644 --- a/message/message.go +++ b/message/message.go @@ -6,6 +6,7 @@ import ( "github.com/google/uuid" ma "github.com/multiformats/go-multiaddr" + perr "github.com/ubclaunchpad/cumulus/errors" ) type ( @@ -74,14 +75,14 @@ func NewRequestMessage(rt ResourceType, p map[string]interface{}) *Message { // resource (if no error occurred). type Response struct { ID string - Error error + Error perr.ProtocolError Resource interface{} } // NewResponseMessage returns a new Message continaing a response with the given // parameters. ResponseMessages should have the same ID as that of the request // message they are a response to. -func NewResponseMessage(id string, err error, resource interface{}) *Message { +func NewResponseMessage(id string, err perr.ProtocolError, resource interface{}) *Message { res := &Response{ ID: id, Error: err, diff --git a/peer/peer.go b/peer/peer.go index 1b37c8c..8dfb43d 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -13,13 +13,13 @@ import ( "github.com/libp2p/go-libp2p-host" "github.com/libp2p/go-libp2p-net" lpeer "github.com/libp2p/go-libp2p-peer" - pstore "github.com/libp2p/go-libp2p-peerstore" + "github.com/libp2p/go-libp2p-peerstore" "github.com/libp2p/go-libp2p-swarm" bhost "github.com/libp2p/go-libp2p/p2p/host/basic" - ma "github.com/multiformats/go-multiaddr" - protoerr "github.com/ubclaunchpad/cumulus/errors" + "github.com/multiformats/go-multiaddr" + perr "github.com/ubclaunchpad/cumulus/errors" "github.com/ubclaunchpad/cumulus/message" - sn "github.com/ubclaunchpad/cumulus/subnet" + "github.com/ubclaunchpad/cumulus/subnet" ) const ( @@ -37,9 +37,9 @@ const ( // Peer is a cumulus Peer composed of a host type Peer struct { host.Host - subnet sn.Subnet + subnet subnet.Subnet listeners map[string]chan message.Response - listenersLock *sync.RWMutex + listenersLock sync.RWMutex } // New creates a Cumulus host with the given IP addr and TCP port. @@ -56,21 +56,21 @@ func New(ip string, port int) (*Peer, error) { pid, _ := lpeer.IDFromPublicKey(pub) // Create a multiaddress (IP address and TCP port for this peer). - addr, err := ma.NewMultiaddr(fmt.Sprintf("/ip4/%s/tcp/%d", ip, port)) + addr, err := multiaddr.NewMultiaddr(fmt.Sprintf("/ip4/%s/tcp/%d", ip, port)) if err != nil { return nil, err } // Create Peerstore and add host's keys to it (avoids annoying err) - ps := pstore.NewPeerstore() + ps := peerstore.NewPeerstore() ps.AddPubKey(pid, pub) ps.AddPrivKey(pid, priv) // Create swarm (this is the interface to the libP2P Network) using the // multiaddress, peerID, and peerStore we just created. - netwrk, err := swarm.NewNetwork( + network, err := swarm.NewNetwork( context.Background(), - []ma.Multiaddr{addr}, + []multiaddr.Multiaddr{addr}, pid, ps, nil) @@ -78,19 +78,19 @@ func New(ip string, port int) (*Peer, error) { return nil, err } - subnet := *sn.New(sn.DefaultMaxPeers) + sn := *subnet.New(subnet.DefaultMaxPeers) // Actually create the host and peer with the network we just set up. - host := bhost.New(netwrk) + host := bhost.New(network) peer := &Peer{ Host: host, - subnet: subnet, + subnet: sn, listeners: make(map[string]chan message.Response), - listenersLock: &sync.RWMutex{}, + listenersLock: sync.RWMutex{}, } // Build host multiaddress - hostAddr, _ := ma.NewMultiaddr(fmt.Sprintf("/ipfs/%s", + hostAddr, _ := multiaddr.NewMultiaddr(fmt.Sprintf("/ipfs/%s", host.ID().Pretty())) // Now we can build a full multiaddress to reach this host @@ -99,7 +99,7 @@ func New(ip string, port int) (*Peer, error) { log.Info("I am ", fullAddr) // Add this host's address to its peerstore (avoid's net/identi error) - ps.AddAddr(pid, fullAddr, pstore.PermanentAddrTTL) + ps.AddAddr(pid, fullAddr, peerstore.PermanentAddrTTL) return peer, nil } @@ -109,21 +109,20 @@ func New(ip string, port int) (*Peer, error) { // peer is initialized. func (p *Peer) Receive(s net.Stream) { // Get remote peer's full multiaddress - remoteMA, err := NewMultiaddr( + ma, err := NewMultiaddr( s.Conn().RemoteMultiaddr(), s.Conn().RemotePeer()) if err != nil { - log.WithError(err).Error( - "Failed to obtain valid remote peer multiaddress") + log.WithError(err).Error("Failed to obtain valid remote peer multiaddress") return } // Add the remote peer to this peer's subnet - err = p.subnet.AddPeer(remoteMA, s) + err = p.subnet.AddPeer(ma.String(), s) if err != nil { // Subnet is full, advertise other available peers and then close // the stream log.WithError(err).Debug("Peer subnet full. Advertising peers...") - msg := message.NewPushMessage(message.ResourcePeerInfo, p.subnet.StringMultiaddrs()) + msg := message.NewPushMessage(message.ResourcePeerInfo, p.subnet.Multiaddrs()) msgErr := msg.Write(s) if msgErr != nil { log.WithError(err).Error("Failed to send ResourcePeerInfo") @@ -131,24 +130,24 @@ func (p *Peer) Receive(s net.Stream) { s.Close() return } - go p.Listen(remoteMA, s) + go p.Listen(ma.String(), s) } // Connect adds the given multiaddress to p's Peerstore and opens a stream // with the peer at that multiaddress if the multiaddress is valid, otherwise // returns error. On success the stream and corresponding multiaddress are // added to this peer's subnet. -func (p *Peer) Connect(peerma string) (net.Stream, error) { - peerid, targetAddr, err := extractPeerInfo(peerma) +func (p *Peer) Connect(sma string) (net.Stream, error) { + pid, targetAddr, err := extractPeerInfo(sma) if err != nil { return nil, err } // Store the peer's address in this host's PeerStore - p.Peerstore().AddAddr(peerid, targetAddr, pstore.PermanentAddrTTL) + p.Peerstore().AddAddr(pid, targetAddr, peerstore.PermanentAddrTTL) // Open a stream with the peer - stream, err := p.NewStream(context.Background(), peerid, CumulusProtocol) + stream, err := p.NewStream(context.Background(), pid, CumulusProtocol) if err != nil { return nil, err } @@ -157,24 +156,25 @@ func (p *Peer) Connect(peerma string) (net.Stream, error) { log.WithError(err).Error("Failed to set read deadline on stream") } - mAddr, err := ma.NewMultiaddr(peerma) - if err != nil { - stream.Close() - return nil, err - } - - err = p.subnet.AddPeer(mAddr, stream) + err = p.subnet.AddPeer(sma, stream) if err != nil { stream.Close() } - go p.Listen(mAddr, stream) + go p.Listen(sma, stream) return stream, nil } // Broadcast sends message to all peers this peer is currently connected to func (p *Peer) Broadcast(m message.Message) error { - return errors.New("Function not implemented") + var err error + for _, ma := range p.subnet.Multiaddrs() { + err = m.Write(p.subnet.Stream(ma)) + if err != nil { + log.WithError(err).Error("Failed to send broadcast message to peer") + } + } + return err } // Request sends a request to a remote peer over the given stream. @@ -203,20 +203,26 @@ func (p *Peer) Request(req message.Request, s net.Stream) (*message.Response, er // Respond responds to a request from another peer func (p *Peer) Respond(req message.Request, s net.Stream) { - response := message.Response{ID: req.ID, Error: nil, Resource: nil} + response := message.Response{ + ID: req.ID, + Error: perr.ProtocolError{}, + } switch req.ResourceType { case message.ResourcePeerInfo: - response.Resource = p.subnet.StringMultiaddrs() + response.Resource = p.subnet.Multiaddrs() break case message.ResourceBlock: - response.Error = protoerr.New(protoerr.NotImplemented) + response.Error = *perr.New(perr.NotImplemented, + "Functionality not yet implemented on this peer") break case message.ResourceTransaction: - response.Error = protoerr.New(protoerr.NotImplemented) + response.Error = *perr.New(perr.NotImplemented, + "Functionality not yet implemented on this peer") break default: - response.Error = protoerr.New(protoerr.InvalidResourceType) + response.Error = *perr.New(perr.InvalidResourceType, + "Invalid resource type") } msg := message.New(message.MessageResponse, response) @@ -225,26 +231,26 @@ func (p *Peer) Respond(req message.Request, s net.Stream) { log.WithError(err).Error("Failed to send reponse") } else { msgJSON, _ := json.Marshal(msg) - log.Info("Sending response: \n%s", string(msgJSON)) + log.Infof("Sending response: \n%s", string(msgJSON)) } } // NewMultiaddr creates a Multiaddress from the given Multiaddress (of the form // /ip4//tcp/) and the peer id (a hash) and turn them // into one Multiaddress. Will return error if Multiaddress is invalid. -func NewMultiaddr(iAddr ma.Multiaddr, pid lpeer.ID) (ma.Multiaddr, error) { +func NewMultiaddr(iAddr multiaddr.Multiaddr, pid lpeer.ID) (multiaddr.Multiaddr, error) { strAddr := iAddr.String() strID := pid.Pretty() strMA := fmt.Sprintf("%s/ipfs/%s", strAddr, strID) - mAddr, err := ma.NewMultiaddr(strMA) + mAddr, err := multiaddr.NewMultiaddr(strMA) return mAddr, err } // HandleMessage responds to a received message -func (p *Peer) HandleMessage(m message.Message, s net.Stream) { +func (p *Peer) handleMessage(m message.Message, s net.Stream) { msgJSON, err := json.Marshal(m) if err == nil { - log.Info("Received message: \n%s", string(msgJSON)) + log.Infof("Received message: \n%s", string(msgJSON)) } switch m.Type { @@ -274,9 +280,9 @@ func (p *Peer) HandleMessage(m message.Message, s net.Stream) { // Listen listens for messages over the stream and responds to them, closing // the given stream and removing the remote peer from this peer's subnet when // done. This should be run as a goroutine. -func (p *Peer) Listen(remoteMA ma.Multiaddr, s net.Stream) { +func (p *Peer) Listen(sma string, s net.Stream) { defer s.Close() - defer p.subnet.RemovePeer(remoteMA) + defer p.subnet.RemovePeer(sma) for { err := s.SetDeadline(time.Now().Add(Timeout)) if err != nil { @@ -289,7 +295,7 @@ func (p *Peer) Listen(remoteMA ma.Multiaddr, s net.Stream) { return } log.Debug("Listener received message") - go p.HandleMessage(*msg, s) + go p.handleMessage(*msg, s) } } @@ -297,16 +303,16 @@ func (p *Peer) Listen(remoteMA ma.Multiaddr, s net.Stream) { // given multiaddress. // Returns peer ID (esentially 46 character hash created by the peer) // and the peer's multiaddress in the form /ip4//tcp/. -func extractPeerInfo(peerma string) (lpeer.ID, ma.Multiaddr, error) { - log.Debug("Extracting peer info from ", peerma) +func extractPeerInfo(sma string) (lpeer.ID, multiaddr.Multiaddr, error) { + log.Debug("Extracting peer info from ", sma) - ipfsaddr, err := ma.NewMultiaddr(peerma) + ipfsaddr, err := multiaddr.NewMultiaddr(sma) if err != nil { return "-", nil, err } // Cannot throw error when passed P_IPFS - pid, err := ipfsaddr.ValueForProtocol(ma.P_IPFS) + pid, err := ipfsaddr.ValueForProtocol(multiaddr.P_IPFS) if err != nil { return "-", nil, err } @@ -316,7 +322,7 @@ func extractPeerInfo(peerma string) (lpeer.ID, ma.Multiaddr, error) { // Decapsulate the /ipfs/ part from the target // /ip4//ipfs/ becomes /ip4/ - targetPeerAddr, err := ma.NewMultiaddr( + targetPeerAddr, err := multiaddr.NewMultiaddr( fmt.Sprintf("/ipfs/%s", lpeer.IDB58Encode(peerid))) if err != nil { return "-", nil, err @@ -341,6 +347,8 @@ func (p *Peer) removeListener(id string) { p.listenersLock.Unlock() } +// getListener synchronously retreives and returns the channel associated with +// the given id func (p *Peer) getListener(id string) chan message.Response { p.listenersLock.RLock() lchan := p.listeners[id] diff --git a/subnet/subnet.go b/subnet/subnet.go index 1c4b750..645e8f3 100644 --- a/subnet/subnet.go +++ b/subnet/subnet.go @@ -2,10 +2,10 @@ package subnet import ( "errors" + "sync" log "github.com/Sirupsen/logrus" - net "github.com/libp2p/go-libp2p-net" - ma "github.com/multiformats/go-multiaddr" + "github.com/libp2p/go-libp2p-net" ) const ( @@ -16,46 +16,55 @@ const ( // Subnet represents the set of peers a peer is connected to at any given time type Subnet struct { - peers map[ma.Multiaddr]net.Stream + peers map[string]net.Stream maxPeers int + lock sync.RWMutex } // New returns a pointer to an empty Subnet with the given maxPeers. func New(maxPeers int) *Subnet { - p := make(map[ma.Multiaddr]net.Stream) - sn := Subnet{maxPeers: maxPeers, peers: p} + p := make(map[string]net.Stream) + sn := Subnet{maxPeers: maxPeers, peers: p, lock: sync.RWMutex{}} return &sn } // AddPeer adds a peer's multiaddress and the corresponding stream between the // local peer and the remote peer to the subnet. If the subnet is already // full, or the multiaddress is not valid, returns error. -func (sn *Subnet) AddPeer(mAddr ma.Multiaddr, stream net.Stream) error { +func (sn *Subnet) AddPeer(sma string, stream net.Stream) error { + sn.lock.Lock() if sn.Full() { + sn.lock.Unlock() return errors.New("Cannot insert new mapping, Subnet is already full") } - // Validate the multiaddress - mAddr, err := ma.NewMultiaddr(mAddr.String()) - if err != nil { - return err - } - // Check if it's already in this subnet - if sn.peers[mAddr] != nil { - log.Debugf("Peer %s is already in subnet", mAddr.String()) + if sn.peers[sma] != nil { + log.Debugf("Peer %s is already in subnet", sma) } else { - log.Debugf("Adding peer %s to subnet", mAddr.String()) - sn.peers[mAddr] = stream + log.Debugf("Adding peer %s to subnet", sma) + sn.peers[sma] = stream } + sn.lock.Unlock() return nil } // RemovePeer removes the mapping with the key mAddr from the subnet if it // exists. -func (sn *Subnet) RemovePeer(mAddr ma.Multiaddr) { - log.Debugf("Removing peer %s from subnet", mAddr.String()) - delete(sn.peers, mAddr) +func (sn *Subnet) RemovePeer(sma string) { + sn.lock.Lock() + log.Debugf("Removing peer %s from subnet", sma) + delete(sn.peers, sma) + sn.lock.Unlock() +} + +// Stream returns the stream associated with the given multiaddr in this subnet. +// Returns nil if the multiaddr is not in this subnet. +func (sn *Subnet) Stream(sma string) net.Stream { + sn.lock.RLock() + stream := sn.peers[sma] + sn.lock.RUnlock() + return stream } // Full returns true if the number of peers in the sunbet is at or over the @@ -65,19 +74,12 @@ func (sn *Subnet) Full() bool { } // Multiaddrs returns a list of all multiaddresses contined in this subnet -func (sn *Subnet) Multiaddrs() []ma.Multiaddr { - mAddrs := make([]ma.Multiaddr, 0, len(sn.peers)) - for mAddr := range sn.peers { - mAddrs = append(mAddrs, mAddr) - } - return mAddrs -} - -// StringMultiaddrs returns a list of all multiaddrs in this subnet as strings -func (sn *Subnet) StringMultiaddrs() []string { - mAddrs := make([]string, 0, len(sn.peers)) - for mAddr := range sn.peers { - mAddrs = append(mAddrs, mAddr.String()) +func (sn *Subnet) Multiaddrs() []string { + sn.lock.RLock() + mas := make([]string, 0, len(sn.peers)) + for sma := range sn.peers { + mas = append(mas, sma) } - return mAddrs + sn.lock.RUnlock() + return mas } From f99226c95f84ee0ea7976cc35ac3e1a071665224 Mon Sep 17 00:00:00 2001 From: Bruno Bachmann Date: Sat, 27 May 2017 16:04:55 -0700 Subject: [PATCH 16/37] Fix TestRequest in Peer --- peer/peer_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/peer/peer_test.go b/peer/peer_test.go index 5b2d7e3..a3b8270 100644 --- a/peer/peer_test.go +++ b/peer/peer_test.go @@ -6,6 +6,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/google/uuid" + protoerr "github.com/ubclaunchpad/cumulus/errors" "github.com/ubclaunchpad/cumulus/message" sn "github.com/ubclaunchpad/cumulus/subnet" ) @@ -202,11 +203,12 @@ func TestRequest(t *testing.T) { Params: nil, } response, err := requester.Request(request, stream) + emptyErr := protoerr.ProtocolError{} if err != nil { fmt.Printf("Failed to make request: %s", err) t.Fail() - } else if response.Error != nil { - fmt.Printf("Remote peer returned response %d", response.Error) + } else if response.Error != emptyErr { + fmt.Printf("Remote peer returned response %s", response.Error) t.Fail() } } From bc62551636f3ee2ea6ad2adf58dacbab6163bfde Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Sat, 27 May 2017 22:06:04 -0700 Subject: [PATCH 17/37] Rework message layer to make gob happy and add tests --- errors/error.go | 58 ----------------- glide.lock | 6 +- glide.yaml | 2 + main.go | 2 +- message/message.go | 124 +++++++++++++++++++----------------- message/message_test.go | 135 ++++++++++++++++++++++++++++++++++++++++ peer/peer.go | 42 ++++++------- 7 files changed, 228 insertions(+), 141 deletions(-) delete mode 100644 errors/error.go create mode 100644 message/message_test.go diff --git a/errors/error.go b/errors/error.go deleted file mode 100644 index 3d0f9e2..0000000 --- a/errors/error.go +++ /dev/null @@ -1,58 +0,0 @@ -package errors - -import "fmt" - -const ( - // InvalidResourceType occurs when a request is received with an unknown - // ResourceType value. - InvalidResourceType = 401 - // SubnetFull occurs when a stream is opened with a peer who's Subnet is - // already full. - SubnetFull = 501 - // NotImplemented occurs when a message or request is received whos response - // requires functionality that does not yet exist. - NotImplemented = 502 -) - -const ( - invalidResourceTypeMsg = "Invalid resource type" - subnetFullMsg = "Failed to add peer to subnet (peer subnet full)" - notImplementedMsg = "Functionality not yet implemented" -) - -// ProtocolError is a container for error messages returned in Peer -// Response bodies. -type ProtocolError struct { - Code int - Message string -} - -// New returns new ProtocolError with the given parameters. -// code argument should be one of the error codes defined above. -func New(code int) *ProtocolError { - var msg string - - switch code { - case SubnetFull: - msg = subnetFullMsg - break - case InvalidResourceType: - msg = invalidResourceTypeMsg - break - case NotImplemented: - msg = notImplementedMsg - break - default: - panic("Attempt to create error with invalid error code") - } - - return &ProtocolError{ - Code: code, - Message: msg, - } -} - -// Error returns the string representation of the given ProtocolError -func (e ProtocolError) Error() string { - return fmt.Sprintf("%d: %s", e.Code, e.Message) -} diff --git a/glide.lock b/glide.lock index 9522746..0a8894e 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 474d608bbb499683dec7db95f6a10f5df61bd5d447651e04567c0d10b816cfe3 -updated: 2017-05-13T15:17:47.793598435-07:00 +hash: 1b81695a566f49c0195d3c273cd75f43c864147343e6beac071fc67daf29b6d5 +updated: 2017-05-27T15:27:09.965588237-07:00 imports: - name: github.com/agl/ed25519 version: 5312a61534124124185d41f09206b9fef1d88403 @@ -25,6 +25,8 @@ imports: subpackages: - proto - io +- name: github.com/google/uuid + version: 064e2069ce9c359c118179501254f67d7d37ba24 - name: github.com/gxed/eventfd version: 80a92cca79a8041496ccc9dd773fcb52a57ec6f9 - name: github.com/gxed/GoEndian diff --git a/glide.yaml b/glide.yaml index 6b0b13f..8868470 100644 --- a/glide.yaml +++ b/glide.yaml @@ -11,3 +11,5 @@ import: - p2p/host/basic - package: github.com/multiformats/go-multiaddr - package: github.com/Sirupsen/logrus +- package: github.com/google/uuid + version: ~0.2.0 diff --git a/main.go b/main.go index 83a0239..379773a 100644 --- a/main.go +++ b/main.go @@ -58,6 +58,6 @@ func main() { return } - host.HandleMessage(*message.NewResponseMessage("", nil, response), stream) + host.HandleMessage(response, stream) host.Close() } diff --git a/message/message.go b/message/message.go index 046291a..8daada6 100644 --- a/message/message.go +++ b/message/message.go @@ -3,9 +3,6 @@ package message import ( "encoding/gob" "io" - - "github.com/google/uuid" - ma "github.com/multiformats/go-multiaddr" ) type ( @@ -13,6 +10,8 @@ type ( Type int // ResourceType specifies the type of a resource in a message. ResourceType int + // ErrorCode is a code associated with an error + ErrorCode int ) const ( @@ -33,19 +32,46 @@ const ( ResourceTransaction ) -// Message is a container for messages, containing a type and either a Request, -// Response, or Push in the payload. -type Message struct { - Type Type - Payload interface{} +const ( + // InvalidResourceType occurs when a request is received with an unknown + // ResourceType value. + InvalidResourceType = 401 + // NotImplemented occurs when a message or request is received whos response + // requires functionality that does not yet exist. + NotImplemented = 501 + // SubnetFull occurs when a stream is opened with a peer who's Subnet is + // already full. + SubnetFull = 503 +) + +// Initializes all the types we need to encode. +func init() { + gob.Register(&Request{}) + gob.Register(&Response{}) + gob.Register(&Push{}) } -// New returns a new Message. -func New(t Type, payload interface{}) *Message { - return &Message{ - Type: t, - Payload: payload, - } +// Error is an error that occured during a request. +type Error struct { + Code ErrorCode + Message string +} + +// NewError returns a new error struct. +func NewError(code ErrorCode, msg string) *Error { + return &Error{code, msg} +} + +// Error returns the error message; to implement `error`. +func (e *Error) Error() string { + return e.Message +} + +// Message is a container for messages, containing a type and either a Request, +// Response, or Push in the payload. +type Message interface { + Type() Type + Write(io.Writer) error } // Request is a container for a request payload, containing a unique request ID, @@ -58,15 +84,9 @@ type Request struct { Params map[string]interface{} } -// NewRequestMessage returns a new Message continaing a request with the given -// parameters. -func NewRequestMessage(rt ResourceType, p map[string]interface{}) *Message { - req := Request{ - ID: uuid.New().String(), - ResourceType: rt, - Params: p, - } - return New(MessageRequest, req) +// Type returns the message type +func (r *Request) Type() Type { + return MessageRequest } // Response is a container for a response payload, containing the unique request @@ -74,20 +94,13 @@ func NewRequestMessage(rt ResourceType, p map[string]interface{}) *Message { // resource (if no error occurred). type Response struct { ID string - Error error + Error *Error Resource interface{} } -// NewResponseMessage returns a new Message continaing a response with the given -// parameters. ResponseMessages should have the same ID as that of the request -// message they are a response to. -func NewResponseMessage(id string, err error, resource interface{}) *Message { - res := &Response{ - ID: id, - Error: err, - Resource: resource, - } - return New(MessageResponse, res) +// Type returns the message type +func (r *Response) Type() Type { + return MessageResponse } // Push is a container for a push payload, containing a resource proactively sent @@ -97,34 +110,33 @@ type Push struct { Resource interface{} } -// NewPushMessage returns a new Message continaing a response with the given -// parameters. -func NewPushMessage(rt ResourceType, resource interface{}) *Message { - res := &Push{ - ResourceType: rt, - Resource: resource, - } - return New(MessageResponse, res) +// Type returns the message type +func (p *Push) Type() Type { + return MessagePush } // Write encodes and writes the Message into the given Writer. -func (m *Message) Write(w io.Writer) error { - return gob.NewEncoder(w).Encode(m) +func (r *Request) Write(w io.Writer) error { + var m Message = r + return gob.NewEncoder(w).Encode(&m) +} + +func (r *Response) Write(w io.Writer) error { + var m Message = r + return gob.NewEncoder(w).Encode(&m) +} + +func (p *Push) Write(w io.Writer) error { + var m Message = p + return gob.NewEncoder(w).Encode(&m) } // Read decodes a message from a Reader and returns it. -func Read(r io.Reader) (*Message, error) { +func Read(r io.Reader) (Message, error) { var m Message err := gob.NewDecoder(r).Decode(&m) - return &m, err -} - -// RegisterGobTypes registers all the types used by gob in reading and writing -// messages. This should only be called once during initializaiton. -func init() { - dummy, _ := ma.NewMultiaddr("") - gob.Register(dummy) - gob.Register(Request{}) - gob.Register(Response{}) - gob.Register(Push{}) + if err != nil { + return nil, err + } + return m, nil } diff --git a/message/message_test.go b/message/message_test.go new file mode 100644 index 0000000..6254563 --- /dev/null +++ b/message/message_test.go @@ -0,0 +1,135 @@ +package message + +import ( + "bytes" + "testing" + + "github.com/google/uuid" +) + +func TestRequest(t *testing.T) { + id := uuid.New().String() + r := Request{ + ID: id, + ResourceType: ResourceTransaction, + } + + var buf bytes.Buffer + + err := r.Write(&buf) + if err != nil { + t.Log(err.Error()) + t.Fail() + } + + out, err := Read(&buf) + if err != nil { + t.Log(err.Error()) + t.Fail() + } + + if out.Type() != MessageRequest { + t.Fail() + } + + outReq, ok := out.(*Request) + if !ok { + t.Fail() + } + + if outReq.ID != id { + t.Fail() + } +} + +func TestResponse(t *testing.T) { + id := uuid.New().String() + r := Response{ + ID: id, + Resource: "resource", + } + + var buf bytes.Buffer + + err := r.Write(&buf) + if err != nil { + t.Log(err.Error()) + t.Fail() + } + + out, err := Read(&buf) + if err != nil { + t.Log(err.Error()) + t.Fail() + } + + if out.Type() != MessageResponse { + t.Fail() + } + + outRes, ok := out.(*Response) + if !ok { + t.Fail() + } + + if outRes.ID != id { + t.Fail() + } + + res, ok := outRes.Resource.(string) + if !ok { + t.Fail() + } + + if res != "resource" { + t.Fail() + } +} + +func TestPush(t *testing.T) { + p := Push{ + ResourceType: ResourceTransaction, + Resource: "transaction", + } + + var buf bytes.Buffer + + err := p.Write(&buf) + if err != nil { + t.Log(err.Error()) + t.Fail() + } + + out, err := Read(&buf) + if err != nil { + t.Log(err.Error()) + t.Fail() + } + + if out.Type() != MessagePush { + t.Fail() + } + + outPush, ok := out.(*Push) + if !ok { + t.Fail() + } + + res, ok := outPush.Resource.(string) + if !ok { + t.Fail() + } + + if res != "transaction" { + t.Fail() + } +} + +func TestError(t *testing.T) { + msg := "Not Implemented" + err := NewError(NotImplemented, msg) + + if err.Error() != msg { + t.Fail() + } +} diff --git a/peer/peer.go b/peer/peer.go index 1b37c8c..9e1ecde 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -17,7 +17,6 @@ import ( "github.com/libp2p/go-libp2p-swarm" bhost "github.com/libp2p/go-libp2p/p2p/host/basic" ma "github.com/multiformats/go-multiaddr" - protoerr "github.com/ubclaunchpad/cumulus/errors" "github.com/ubclaunchpad/cumulus/message" sn "github.com/ubclaunchpad/cumulus/subnet" ) @@ -38,7 +37,7 @@ const ( type Peer struct { host.Host subnet sn.Subnet - listeners map[string]chan message.Response + listeners map[string]chan *message.Response listenersLock *sync.RWMutex } @@ -85,7 +84,7 @@ func New(ip string, port int) (*Peer, error) { peer := &Peer{ Host: host, subnet: subnet, - listeners: make(map[string]chan message.Response), + listeners: make(map[string]chan *message.Response), listenersLock: &sync.RWMutex{}, } @@ -123,7 +122,7 @@ func (p *Peer) Receive(s net.Stream) { // Subnet is full, advertise other available peers and then close // the stream log.WithError(err).Debug("Peer subnet full. Advertising peers...") - msg := message.NewPushMessage(message.ResourcePeerInfo, p.subnet.StringMultiaddrs()) + msg := message.Push{ResourceType: message.ResourcePeerInfo, Resource: p.subnet.StringMultiaddrs} msgErr := msg.Write(s) if msgErr != nil { log.WithError(err).Error("Failed to send ResourcePeerInfo") @@ -186,8 +185,7 @@ func (p *Peer) Request(req message.Request, s net.Stream) (*message.Response, er defer p.removeListener(req.ID) // Send request - reqMsg := message.New(message.MessageRequest, req) - err := reqMsg.Write(s) + err := req.Write(s) if err != nil { return nil, err } @@ -195,37 +193,33 @@ func (p *Peer) Request(req message.Request, s net.Stream) (*message.Response, er // Receive response or timeout select { case res := <-lchan: - return &res, nil + return res, nil case <-time.After(Timeout): return nil, errors.New("Timed out waiting for response") } } // Respond responds to a request from another peer -func (p *Peer) Respond(req message.Request, s net.Stream) { - response := message.Response{ID: req.ID, Error: nil, Resource: nil} +func (p *Peer) Respond(req *message.Request, s net.Stream) { + response := message.Response{ID: req.ID} switch req.ResourceType { case message.ResourcePeerInfo: response.Resource = p.subnet.StringMultiaddrs() break case message.ResourceBlock: - response.Error = protoerr.New(protoerr.NotImplemented) + // response.Error = protoerr.New(protoerr.NotImplemented) break case message.ResourceTransaction: - response.Error = protoerr.New(protoerr.NotImplemented) + // response.Error = protoerr.New(protoerr.NotImplemented) break default: - response.Error = protoerr.New(protoerr.InvalidResourceType) + // response.Error = protoerr.New(protoerr.InvalidResourceType) } - msg := message.New(message.MessageResponse, response) - err := msg.Write(s) + err := response.Write(s) if err != nil { log.WithError(err).Error("Failed to send reponse") - } else { - msgJSON, _ := json.Marshal(msg) - log.Info("Sending response: \n%s", string(msgJSON)) } } @@ -247,14 +241,14 @@ func (p *Peer) HandleMessage(m message.Message, s net.Stream) { log.Info("Received message: \n%s", string(msgJSON)) } - switch m.Type { + switch m.Type() { case message.MessageRequest: // Respond to the request by sending request resource - p.Respond(m.Payload.(message.Request), s) + p.Respond(m.(*message.Request), s) break case message.MessageResponse: // Pass the response to the goroutine that requested it - res := m.Payload.(message.Response) + res := m.(*message.Response) lchan := p.getListener(res.ID) if lchan != nil { log.Debug("Found listener channel for response") @@ -289,7 +283,7 @@ func (p *Peer) Listen(remoteMA ma.Multiaddr, s net.Stream) { return } log.Debug("Listener received message") - go p.HandleMessage(*msg, s) + go p.HandleMessage(msg, s) } } @@ -327,9 +321,9 @@ func extractPeerInfo(peerma string) (lpeer.ID, ma.Multiaddr, error) { } // newListener synchronously adds a listener to this peer's listeners map -func (p *Peer) newListener(id string) chan message.Response { +func (p *Peer) newListener(id string) chan *message.Response { p.listenersLock.Lock() - p.listeners[id] = make(chan message.Response) + p.listeners[id] = make(chan *message.Response) p.listenersLock.Unlock() return p.listeners[id] } @@ -341,7 +335,7 @@ func (p *Peer) removeListener(id string) { p.listenersLock.Unlock() } -func (p *Peer) getListener(id string) chan message.Response { +func (p *Peer) getListener(id string) chan *message.Response { p.listenersLock.RLock() lchan := p.listeners[id] p.listenersLock.RUnlock() From 0d890ffba2af6ce556f349360beb49a3ac4d444c Mon Sep 17 00:00:00 2001 From: Bruno Bachmann Date: Sun, 28 May 2017 11:40:55 -0700 Subject: [PATCH 18/37] Refactor peer's stream --- errors/error.go | 42 ---------------------- main.go | 2 +- peer/peer.go | 92 +++++++++++++++++++---------------------------- peer/peer_test.go | 6 ++-- stream/stream.go | 49 +++++++++++++++++++++++++ subnet/subnet.go | 14 ++++---- 6 files changed, 95 insertions(+), 110 deletions(-) delete mode 100644 errors/error.go create mode 100644 stream/stream.go diff --git a/errors/error.go b/errors/error.go deleted file mode 100644 index 0c35e9c..0000000 --- a/errors/error.go +++ /dev/null @@ -1,42 +0,0 @@ -package errors - -import "fmt" - -const ( - // InvalidResourceType occurs when a request is received with an unknown - // ResourceType value. - InvalidResourceType = 401 - // SubnetFull occurs when a stream is opened with a peer who's Subnet is - // already full. - SubnetFull = 501 - // NotImplemented occurs when a message or request is received whos response - // requires functionality that does not yet exist. - NotImplemented = 502 -) - -const ( - invalidResourceTypeMsg = "Invalid resource type" - subnetFullMsg = "Failed to add peer to subnet (peer subnet full)" - notImplementedMsg = "Functionality not yet implemented" -) - -// ProtocolError is a container for error messages returned in Peer -// Response bodies. -type ProtocolError struct { - Code int - Message string -} - -// New returns new ProtocolError with the given parameters. -// code argument should be one of the error codes defined above. -func New(code int, msg string) *ProtocolError { - return &ProtocolError{ - Code: code, - Message: msg, - } -} - -// Error returns the string representation of the given ProtocolError -func (e ProtocolError) Error() string { - return fmt.Sprintf("%d: %s", e.Code, e.Message) -} diff --git a/main.go b/main.go index 7dddee7..8ca6abd 100644 --- a/main.go +++ b/main.go @@ -52,7 +52,7 @@ func main() { ID: uuid.New().String(), ResourceType: message.ResourcePeerInfo, } - _, err = host.Request(request, stream) + _, err = host.Request(request, *stream) if err != nil { log.WithError(err).Error("Error writing message to stream") return diff --git a/peer/peer.go b/peer/peer.go index 3b8dce1..e75802c 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -5,19 +5,19 @@ import ( "encoding/json" "errors" "fmt" - "sync" "time" log "github.com/Sirupsen/logrus" "github.com/libp2p/go-libp2p-crypto" "github.com/libp2p/go-libp2p-host" - "github.com/libp2p/go-libp2p-net" + net "github.com/libp2p/go-libp2p-net" lpeer "github.com/libp2p/go-libp2p-peer" "github.com/libp2p/go-libp2p-peerstore" "github.com/libp2p/go-libp2p-swarm" bhost "github.com/libp2p/go-libp2p/p2p/host/basic" multiaddr "github.com/multiformats/go-multiaddr" "github.com/ubclaunchpad/cumulus/message" + "github.com/ubclaunchpad/cumulus/stream" "github.com/ubclaunchpad/cumulus/subnet" ) @@ -36,9 +36,7 @@ const ( // Peer is a cumulus Peer composed of a host type Peer struct { host.Host - subnet sn.Subnet - listeners map[string]chan *message.Response - listenersLock *sync.RWMutex + subnet subnet.Subnet } // New creates a Cumulus host with the given IP addr and TCP port. @@ -82,10 +80,8 @@ func New(ip string, port int) (*Peer, error) { // Actually create the host and peer with the network we just set up. host := bhost.New(network) peer := &Peer{ - Host: host, - subnet: subnet, - listeners: make(map[string]chan *message.Response), - listenersLock: &sync.RWMutex{}, + Host: host, + subnet: sn, } // Build host multiaddress @@ -108,20 +104,24 @@ func New(ip string, port int) (*Peer, error) { // peer is initialized. func (p *Peer) Receive(s net.Stream) { // Get remote peer's full multiaddress - ma, err := NewMultiaddr( - s.Conn().RemoteMultiaddr(), s.Conn().RemotePeer()) + ma, err := NewMultiaddr(s.Conn().RemoteMultiaddr(), s.Conn().RemotePeer()) if err != nil { log.WithError(err).Error("Failed to obtain valid remote peer multiaddress") return } + peerstream := *stream.New(s) + // Add the remote peer to this peer's subnet - err = p.subnet.AddPeer(ma.String(), s) + err = p.subnet.AddPeer(ma.String(), peerstream) if err != nil { // Subnet is full, advertise other available peers and then close // the stream log.WithError(err).Debug("Peer subnet full. Advertising peers...") - msg := message.Push{ResourceType: message.ResourcePeerInfo, Resource: p.subnet.StringMultiaddrs} + msg := message.Push{ + ResourceType: message.ResourcePeerInfo, + Resource: p.subnet.Multiaddrs(), + } msgErr := msg.Write(s) if msgErr != nil { log.WithError(err).Error("Failed to send ResourcePeerInfo") @@ -129,14 +129,14 @@ func (p *Peer) Receive(s net.Stream) { s.Close() return } - go p.Listen(ma.String(), s) + go p.Listen(ma.String(), peerstream) } // Connect adds the given multiaddress to p's Peerstore and opens a stream // with the peer at that multiaddress if the multiaddress is valid, otherwise // returns error. On success the stream and corresponding multiaddress are // added to this peer's subnet. -func (p *Peer) Connect(sma string) (net.Stream, error) { +func (p *Peer) Connect(sma string) (*stream.Stream, error) { pid, targetAddr, err := extractPeerInfo(sma) if err != nil { return nil, err @@ -146,22 +146,24 @@ func (p *Peer) Connect(sma string) (net.Stream, error) { p.Peerstore().AddAddr(pid, targetAddr, peerstore.PermanentAddrTTL) // Open a stream with the peer - stream, err := p.NewStream(context.Background(), pid, CumulusProtocol) + s, err := p.NewStream(context.Background(), pid, CumulusProtocol) if err != nil { return nil, err } - err = stream.SetDeadline(time.Now().Add(Timeout)) + err = s.SetDeadline(time.Now().Add(Timeout)) if err != nil { log.WithError(err).Error("Failed to set read deadline on stream") } - err = p.subnet.AddPeer(sma, stream) + // Make a stream.Stream out of a net.Stream (sorry) + peerstream := *stream.New(s) + err = p.subnet.AddPeer(sma, peerstream) if err != nil { - stream.Close() + s.Close() } - go p.Listen(sma, stream) - return stream, nil + go p.Listen(sma, peerstream) + return &peerstream, nil } // Broadcast sends message to all peers this peer is currently connected to @@ -179,10 +181,10 @@ func (p *Peer) Broadcast(m message.Message) error { // Request sends a request to a remote peer over the given stream. // Returns response if a response was received, otherwise returns error. // You should typically run this function in a goroutine -func (p *Peer) Request(req message.Request, s net.Stream) (*message.Response, error) { +func (p *Peer) Request(req message.Request, s stream.Stream) (*message.Response, error) { // Set up a listen channel to listen for the response (remove when done) - lchan := p.newListener(req.ID) - defer p.removeListener(req.ID) + lchan := s.NewListener(req.ID) + defer s.RemoveListener(req.ID) // Send request err := req.Write(s) @@ -200,7 +202,7 @@ func (p *Peer) Request(req message.Request, s net.Stream) (*message.Response, er } // Respond responds to a request from another peer -func (p *Peer) Respond(req *message.Request, s net.Stream) { +func (p *Peer) Respond(req *message.Request, s stream.Stream) { response := message.Response{ID: req.ID} switch req.ResourceType { @@ -208,20 +210,20 @@ func (p *Peer) Respond(req *message.Request, s net.Stream) { response.Resource = p.subnet.Multiaddrs() break case message.ResourceBlock: - // response.Error = protoerr.New(protoerr.NotImplemented) - break case message.ResourceTransaction: - // response.Error = protoerr.New(protoerr.NotImplemented) + response.Error = message.NewError(message.NotImplemented, + "Functionality not implemented on this peer") break default: - // response.Error = protoerr.New(protoerr.InvalidResourceType) + response.Error = message.NewError(message.InvalidResourceType, + "Invalid resource type") } err := response.Write(s) if err != nil { log.WithError(err).Error("Failed to send reponse") } else { - msgJSON, _ := json.Marshal(msg) + msgJSON, _ := json.Marshal(response) log.Infof("Sending response: \n%s", string(msgJSON)) } } @@ -238,7 +240,7 @@ func NewMultiaddr(iAddr multiaddr.Multiaddr, pid lpeer.ID) (multiaddr.Multiaddr, } // HandleMessage responds to a received message -func (p *Peer) handleMessage(m message.Message, s net.Stream) { +func (p *Peer) handleMessage(m message.Message, s stream.Stream) { msgJSON, err := json.Marshal(m) if err == nil { log.Infof("Received message: \n%s", string(msgJSON)) @@ -252,7 +254,7 @@ func (p *Peer) handleMessage(m message.Message, s net.Stream) { case message.MessageResponse: // Pass the response to the goroutine that requested it res := m.(*message.Response) - lchan := p.getListener(res.ID) + lchan := s.Listener(res.ID) if lchan != nil { log.Debug("Found listener channel for response") lchan <- res @@ -271,7 +273,7 @@ func (p *Peer) handleMessage(m message.Message, s net.Stream) { // Listen listens for messages over the stream and responds to them, closing // the given stream and removing the remote peer from this peer's subnet when // done. This should be run as a goroutine. -func (p *Peer) Listen(sma string, s net.Stream) { +func (p *Peer) Listen(sma string, s stream.Stream) { defer s.Close() defer p.subnet.RemovePeer(sma) for { @@ -286,7 +288,7 @@ func (p *Peer) Listen(sma string, s net.Stream) { return } log.Debug("Listener received message") - go p.HandleMessage(msg, s) + go p.handleMessage(msg, s) } } @@ -322,25 +324,3 @@ func extractPeerInfo(sma string) (lpeer.ID, multiaddr.Multiaddr, error) { trgtAddr := ipfsaddr.Decapsulate(targetPeerAddr) return peerid, trgtAddr, nil } - -// newListener synchronously adds a listener to this peer's listeners map -func (p *Peer) newListener(id string) chan *message.Response { - p.listenersLock.Lock() - p.listeners[id] = make(chan *message.Response) - p.listenersLock.Unlock() - return p.listeners[id] -} - -// removeListener synchronously removes a listener from this peer's listeners map -func (p *Peer) removeListener(id string) { - p.listenersLock.Lock() - delete(p.listeners, id) - p.listenersLock.Unlock() -} - -func (p *Peer) getListener(id string) chan *message.Response { - p.listenersLock.RLock() - lchan := p.listeners[id] - p.listenersLock.RUnlock() - return lchan -} diff --git a/peer/peer_test.go b/peer/peer_test.go index 4fa820e..3653564 100644 --- a/peer/peer_test.go +++ b/peer/peer_test.go @@ -6,7 +6,6 @@ import ( log "github.com/Sirupsen/logrus" "github.com/google/uuid" - protoerr "github.com/ubclaunchpad/cumulus/errors" "github.com/ubclaunchpad/cumulus/message" sn "github.com/ubclaunchpad/cumulus/subnet" ) @@ -202,12 +201,11 @@ func TestRequest(t *testing.T) { ResourceType: message.ResourcePeerInfo, Params: nil, } - response, err := requester.Request(request, stream) - emptyErr := protoerr.ProtocolError{} + response, err := requester.Request(request, *stream) if err != nil { fmt.Printf("Failed to make request: %s", err) t.FailNow() - } else if response.Error != emptyErr { + } else if response.Error != nil { fmt.Printf("Remote peer returned response %s", response.Error) t.FailNow() } diff --git a/stream/stream.go b/stream/stream.go new file mode 100644 index 0000000..67ebf9f --- /dev/null +++ b/stream/stream.go @@ -0,0 +1,49 @@ +package stream + +import ( + "sync" + + "github.com/libp2p/go-libp2p-net" + "github.com/ubclaunchpad/cumulus/message" +) + +// Stream is a synchronized container for net.Stream and a mapping of listener +// ids to response channels +type Stream struct { + net.Stream + listeners map[string]chan *message.Response + lock *sync.RWMutex +} + +// New returns a new Stream containing the given net.Stream +func New(s net.Stream) *Stream { + return &Stream{ + s, + make(map[string]chan *message.Response), + &sync.RWMutex{}, + } +} + +// NewListener synchronously adds a listener to this peer's listeners map +func (s *Stream) NewListener(id string) chan *message.Response { + s.lock.Lock() + s.listeners[id] = make(chan *message.Response) + s.lock.Unlock() + return s.listeners[id] +} + +// RemoveListener synchronously removes a listener from this peer's listeners map +func (s *Stream) RemoveListener(id string) { + s.lock.Lock() + delete(s.listeners, id) + s.lock.Unlock() +} + +// Listener synchronously retrieves the channel the listener with the given +// request/response id is waiting on +func (s *Stream) Listener(id string) chan *message.Response { + s.lock.RLock() + lchan := s.listeners[id] + s.lock.RUnlock() + return lchan +} diff --git a/subnet/subnet.go b/subnet/subnet.go index 645e8f3..6f5803d 100644 --- a/subnet/subnet.go +++ b/subnet/subnet.go @@ -5,7 +5,7 @@ import ( "sync" log "github.com/Sirupsen/logrus" - "github.com/libp2p/go-libp2p-net" + "github.com/ubclaunchpad/cumulus/stream" ) const ( @@ -16,14 +16,14 @@ const ( // Subnet represents the set of peers a peer is connected to at any given time type Subnet struct { - peers map[string]net.Stream + peers map[string]*stream.Stream maxPeers int lock sync.RWMutex } // New returns a pointer to an empty Subnet with the given maxPeers. func New(maxPeers int) *Subnet { - p := make(map[string]net.Stream) + p := make(map[string]*stream.Stream) sn := Subnet{maxPeers: maxPeers, peers: p, lock: sync.RWMutex{}} return &sn } @@ -31,7 +31,7 @@ func New(maxPeers int) *Subnet { // AddPeer adds a peer's multiaddress and the corresponding stream between the // local peer and the remote peer to the subnet. If the subnet is already // full, or the multiaddress is not valid, returns error. -func (sn *Subnet) AddPeer(sma string, stream net.Stream) error { +func (sn *Subnet) AddPeer(sma string, stream stream.Stream) error { sn.lock.Lock() if sn.Full() { sn.lock.Unlock() @@ -43,7 +43,7 @@ func (sn *Subnet) AddPeer(sma string, stream net.Stream) error { log.Debugf("Peer %s is already in subnet", sma) } else { log.Debugf("Adding peer %s to subnet", sma) - sn.peers[sma] = stream + sn.peers[sma] = &stream } sn.lock.Unlock() return nil @@ -60,9 +60,9 @@ func (sn *Subnet) RemovePeer(sma string) { // Stream returns the stream associated with the given multiaddr in this subnet. // Returns nil if the multiaddr is not in this subnet. -func (sn *Subnet) Stream(sma string) net.Stream { +func (sn *Subnet) Stream(sma string) stream.Stream { sn.lock.RLock() - stream := sn.peers[sma] + stream := *sn.peers[sma] sn.lock.RUnlock() return stream } From dfd309e5c5a94708efb59e2e130337ee99e6f9bc Mon Sep 17 00:00:00 2001 From: Bruno Bachmann Date: Mon, 29 May 2017 22:37:39 -0700 Subject: [PATCH 19/37] Add stream tests, update Error to ProtocolError --- message/message.go | 14 ++++---- message/message_test.go | 2 +- peer/peer.go | 4 +-- stream/stream_test.go | 76 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 10 deletions(-) create mode 100644 stream/stream_test.go diff --git a/message/message.go b/message/message.go index 8daada6..1dce108 100644 --- a/message/message.go +++ b/message/message.go @@ -51,19 +51,19 @@ func init() { gob.Register(&Push{}) } -// Error is an error that occured during a request. -type Error struct { +// ProtocolError is an error that occured during a request. +type ProtocolError struct { Code ErrorCode Message string } -// NewError returns a new error struct. -func NewError(code ErrorCode, msg string) *Error { - return &Error{code, msg} +// NewProtocolError returns a new error struct. +func NewProtocolError(code ErrorCode, msg string) *ProtocolError { + return &ProtocolError{code, msg} } // Error returns the error message; to implement `error`. -func (e *Error) Error() string { +func (e *ProtocolError) Error() string { return e.Message } @@ -94,7 +94,7 @@ func (r *Request) Type() Type { // resource (if no error occurred). type Response struct { ID string - Error *Error + Error *ProtocolError Resource interface{} } diff --git a/message/message_test.go b/message/message_test.go index 6254563..f7816d8 100644 --- a/message/message_test.go +++ b/message/message_test.go @@ -127,7 +127,7 @@ func TestPush(t *testing.T) { func TestError(t *testing.T) { msg := "Not Implemented" - err := NewError(NotImplemented, msg) + err := NewProtocolError(NotImplemented, msg) if err.Error() != msg { t.Fail() diff --git a/peer/peer.go b/peer/peer.go index e75802c..915e946 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -211,11 +211,11 @@ func (p *Peer) Respond(req *message.Request, s stream.Stream) { break case message.ResourceBlock: case message.ResourceTransaction: - response.Error = message.NewError(message.NotImplemented, + response.Error = message.NewProtocolError(message.NotImplemented, "Functionality not implemented on this peer") break default: - response.Error = message.NewError(message.InvalidResourceType, + response.Error = message.NewProtocolError(message.InvalidResourceType, "Invalid resource type") } diff --git a/stream/stream_test.go b/stream/stream_test.go new file mode 100644 index 0000000..e78ad49 --- /dev/null +++ b/stream/stream_test.go @@ -0,0 +1,76 @@ +package stream + +import ( + "testing" + "time" + + "github.com/google/uuid" + "github.com/ubclaunchpad/cumulus/message" +) + +func TestNewListener(t *testing.T) { + stream := New(nil) + resource := map[string]int{ + "hello": 1, + "world": 500, + } + response := &message.Response{ + ID: uuid.New().String(), + Error: message.NewProtocolError(message.InvalidResourceType, "Invalid resource type"), + Resource: resource, + } + lchan := stream.NewListener(response.ID) + + go func(lchan chan *message.Response, response *message.Response) { + var received *message.Response + select { + case received = <-lchan: + break + case <-time.After(time.Second * 3): + t.FailNow() + } + if *received != *response { + t.FailNow() + } + if received.Resource.(map[string]int)["hello"] != 1 || + received.Resource.(map[string]int)["world"] != 500 { + t.FailNow() + } + }(lchan, response) + + go func(lchan chan *message.Response, response *message.Response) { + lchan <- response + }(lchan, response) +} + +func TestAsyncListeners(t *testing.T) { + stream := New(nil) + numlisteners := 10 + + testRoutine := func(stream *Stream, numlisteners int) { + ids := make([]string, numlisteners) + lchans := make([]chan *message.Response, numlisteners) + for i := 0; i < numlisteners; i++ { + ids[i] = uuid.New().String() + lchans[i] = stream.NewListener(ids[i]) + } + for i := 0; i < numlisteners; i++ { + lchan := stream.Listener(ids[i]) + if lchan != lchans[i] { + t.FailNow() + } + } + for i := 0; i < numlisteners; i++ { + stream.RemoveListener(ids[i]) + lchan := stream.Listener(ids[i]) + if lchan != nil { + t.FailNow() + } + } + } + + go testRoutine(stream, numlisteners) + go testRoutine(stream, numlisteners) + go testRoutine(stream, numlisteners) + go testRoutine(stream, numlisteners) +} From 6b804413e4a6e4fab9892e155a269b21aaae97c3 Mon Sep 17 00:00:00 2001 From: Bruno Bachmann Date: Mon, 29 May 2017 22:48:05 -0700 Subject: [PATCH 20/37] Fix bug in stream and TestNewListener --- stream/stream.go | 3 ++- stream/stream_test.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/stream/stream.go b/stream/stream.go index 67ebf9f..885d4d0 100644 --- a/stream/stream.go +++ b/stream/stream.go @@ -28,8 +28,9 @@ func New(s net.Stream) *Stream { func (s *Stream) NewListener(id string) chan *message.Response { s.lock.Lock() s.listeners[id] = make(chan *message.Response) + lchan := s.listeners[id] s.lock.Unlock() - return s.listeners[id] + return lchan } // RemoveListener synchronously removes a listener from this peer's listeners map diff --git a/stream/stream_test.go b/stream/stream_test.go index e78ad49..2635fe8 100644 --- a/stream/stream_test.go +++ b/stream/stream_test.go @@ -29,7 +29,7 @@ func TestNewListener(t *testing.T) { case <-time.After(time.Second * 3): t.FailNow() } - if *received != *response { + if received.ID != response.ID || received.Error != response.Error { t.FailNow() } if received.Resource.(map[string]int)["hello"] != 1 || From 906121492f8ab1603717c288fbfdd51e20948ad3 Mon Sep 17 00:00:00 2001 From: Bruno Bachmann Date: Tue, 30 May 2017 22:20:43 -0700 Subject: [PATCH 21/37] Update and add Peer tests --- peer/peer.go | 4 ++- peer/peer_test.go | 71 +++++++++++++++++++++++++++++++++++------------ 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/peer/peer.go b/peer/peer.go index 915e946..8af8d5c 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -118,7 +118,7 @@ func (p *Peer) Receive(s net.Stream) { // Subnet is full, advertise other available peers and then close // the stream log.WithError(err).Debug("Peer subnet full. Advertising peers...") - msg := message.Push{ + msg := &message.Push{ ResourceType: message.ResourcePeerInfo, Resource: p.subnet.Multiaddrs(), } @@ -153,6 +153,7 @@ func (p *Peer) Connect(sma string) (*stream.Stream, error) { err = s.SetDeadline(time.Now().Add(Timeout)) if err != nil { log.WithError(err).Error("Failed to set read deadline on stream") + return nil, err } // Make a stream.Stream out of a net.Stream (sorry) @@ -160,6 +161,7 @@ func (p *Peer) Connect(sma string) (*stream.Stream, error) { err = p.subnet.AddPeer(sma, peerstream) if err != nil { s.Close() + return nil, err } go p.Listen(sma, peerstream) diff --git a/peer/peer_test.go b/peer/peer_test.go index 3653564..61a8c08 100644 --- a/peer/peer_test.go +++ b/peer/peer_test.go @@ -7,7 +7,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/google/uuid" "github.com/ubclaunchpad/cumulus/message" - sn "github.com/ubclaunchpad/cumulus/subnet" + "github.com/ubclaunchpad/cumulus/subnet" ) func TestMain(t *testing.T) { @@ -133,43 +133,78 @@ func TestReceiveInvalidAddress(t *testing.T) { } } -func TestSubnetFull(t *testing.T) { - testPeer, err := New("127.0.0.1", 8080) +func TestConnectWithSubnetFull(t *testing.T) { + testPeer, err := New(DefaultIP, DefaultPort) if err != nil { t.Fail() } - testPeer.SetStreamHandler(CumulusProtocol, testPeer.Receive) - peers := make([]*Peer, sn.DefaultMaxPeers) + peers := make([]*Peer, subnet.DefaultMaxPeers) - for i := 1; i < sn.DefaultMaxPeers; i++ { - peers[i], err = New("127.0.0.1", 8080+i) + for i := 0; i < subnet.DefaultMaxPeers; i++ { + peers[i], err = New(DefaultIP, 8080+i) if err != nil { - fmt.Println("Failed trying to create a new test peer") - t.Fail() + t.FailNow() } peers[i].SetStreamHandler(CumulusProtocol, peers[i].Receive) ma, maErr := NewMultiaddr(peers[i].Addrs()[0], peers[i].ID()) if maErr != nil { - t.Fail() + t.FailNow() } _, err = testPeer.Connect(ma.String()) if err != nil { - fmt.Println("Failed trying to connect to a test peer") - fmt.Println(ma.String()) - t.Fail() + t.FailNow() } } - lastPeer, err := New("127.0.0.1", 8081+sn.DefaultMaxPeers) + lastPeer, err := New(DefaultIP, 8081+subnet.DefaultMaxPeers) if err != nil { - fmt.Println("Failed trying to create the last test peer") - t.Fail() + t.FailNow() } _, err = testPeer.Connect(lastPeer.Addrs()[0].String()) if err == nil { - fmt.Println("Failed trying to connect to the last test peer") - t.Fail() + t.FailNow() + } +} + +func TestReceiveWithSubnetFull(t *testing.T) { + targetPeer, err := New(DefaultIP, DefaultPort) + if err != nil { + t.FailNow() + } + targetPeer.SetStreamHandler(CumulusProtocol, targetPeer.Receive) + ma, err := NewMultiaddr(targetPeer.Addrs()[0], targetPeer.ID()) + if err != nil { + t.FailNow() + } + + for i := 0; i < subnet.DefaultMaxPeers; i++ { + p, err := New(DefaultIP, 8080+i) + if err != nil { + t.FailNow() + } + _, err = p.Connect(ma.String()) + if err != nil { + t.FailNow() + } + } + + lastPeer, err := New(DefaultIP, 8080+subnet.DefaultMaxPeers) + if err != nil { + t.FailNow() } + + stream, err := lastPeer.Connect(ma.String()) + if err != nil { + t.FailNow() + } + + msg, err := message.Read(stream) + if err != nil || + msg.Type() != message.MessagePush || + msg.(*message.Push).ResourceType != message.ResourcePeerInfo { + t.FailNow() + } + } func TestRequest(t *testing.T) { From 3c10a49b7f04939fa151c07723071629634d6603 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Thu, 1 Jun 2017 22:50:46 -0700 Subject: [PATCH 22/37] fix failing test --- peer/peer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peer/peer.go b/peer/peer.go index 8af8d5c..056f1b5 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -118,7 +118,7 @@ func (p *Peer) Receive(s net.Stream) { // Subnet is full, advertise other available peers and then close // the stream log.WithError(err).Debug("Peer subnet full. Advertising peers...") - msg := &message.Push{ + msg := message.Push{ ResourceType: message.ResourcePeerInfo, Resource: p.subnet.Multiaddrs(), } From d3d9824a940ae60bb3e7b84a22ae65a6fd4b9825 Mon Sep 17 00:00:00 2001 From: Bruno Bachmann Date: Sat, 3 Jun 2017 11:17:00 -0700 Subject: [PATCH 23/37] Add handlePushMessage to Peer, fix peer test --- main.go | 3 ++- peer/peer.go | 65 ++++++++++++++++++++++++++++++----------------- peer/peer_test.go | 10 +------- 3 files changed, 45 insertions(+), 33 deletions(-) diff --git a/main.go b/main.go index 8ca6abd..ef5e344 100644 --- a/main.go +++ b/main.go @@ -17,7 +17,7 @@ func main() { // when joining the Cumulus Network. // port is the port to communicate over (defaults to peer.DefaultPort). // ip is the public IP address of the this host. - targetPeer := flag.String("t", "", "target peer to connect to") + targetPeer := flag.String("t", "", "Target peer to connect to") port := flag.Int("p", peer.DefaultPort, "TCP port to use for this host") ip := flag.String("i", peer.DefaultIP, "IP address to use for this host") debug := flag.Bool("d", false, "Enable debug logging") @@ -57,5 +57,6 @@ func main() { log.WithError(err).Error("Error writing message to stream") return } + host.Close() } diff --git a/peer/peer.go b/peer/peer.go index 8af8d5c..7559668 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -36,7 +36,7 @@ const ( // Peer is a cumulus Peer composed of a host type Peer struct { host.Host - subnet subnet.Subnet + Subnet subnet.Subnet } // New creates a Cumulus host with the given IP addr and TCP port. @@ -81,7 +81,7 @@ func New(ip string, port int) (*Peer, error) { host := bhost.New(network) peer := &Peer{ Host: host, - subnet: sn, + Subnet: sn, } // Build host multiaddress @@ -113,29 +113,30 @@ func (p *Peer) Receive(s net.Stream) { peerstream := *stream.New(s) // Add the remote peer to this peer's subnet - err = p.subnet.AddPeer(ma.String(), peerstream) + err = p.Subnet.AddPeer(ma.String(), peerstream) if err != nil { // Subnet is full, advertise other available peers and then close // the stream - log.WithError(err).Debug("Peer subnet full. Advertising peers...") - msg := &message.Push{ + log.WithError(err).Info("Peer subnet full. Advertising peers...") + msg := message.Push{ ResourceType: message.ResourcePeerInfo, - Resource: p.subnet.Multiaddrs(), + Resource: p.Subnet.Multiaddrs(), } msgErr := msg.Write(s) if msgErr != nil { log.WithError(err).Error("Failed to send ResourcePeerInfo") } + s.Close() return } - go p.Listen(ma.String(), peerstream) + go p.listen(ma.String(), peerstream) } // Connect adds the given multiaddress to p's Peerstore and opens a stream // with the peer at that multiaddress if the multiaddress is valid, otherwise -// returns error. On success the stream and corresponding multiaddress are -// added to this peer's subnet. +// returns error. This also spawns a listener that will receive and handle +// messages received over the stream with the peer we connected to. func (p *Peer) Connect(sma string) (*stream.Stream, error) { pid, targetAddr, err := extractPeerInfo(sma) if err != nil { @@ -158,21 +159,21 @@ func (p *Peer) Connect(sma string) (*stream.Stream, error) { // Make a stream.Stream out of a net.Stream (sorry) peerstream := *stream.New(s) - err = p.subnet.AddPeer(sma, peerstream) + err = p.Subnet.AddPeer(sma, peerstream) if err != nil { s.Close() return nil, err } - go p.Listen(sma, peerstream) + go p.listen(sma, peerstream) return &peerstream, nil } // Broadcast sends message to all peers this peer is currently connected to func (p *Peer) Broadcast(m message.Message) error { var err error - for _, ma := range p.subnet.Multiaddrs() { - err = m.Write(p.subnet.Stream(ma)) + for _, ma := range p.Subnet.Multiaddrs() { + err = m.Write(p.Subnet.Stream(ma)) if err != nil { log.WithError(err).Error("Failed to send broadcast message to peer") } @@ -182,7 +183,7 @@ func (p *Peer) Broadcast(m message.Message) error { // Request sends a request to a remote peer over the given stream. // Returns response if a response was received, otherwise returns error. -// You should typically run this function in a goroutine +// You should typically run this function as a goroutine func (p *Peer) Request(req message.Request, s stream.Stream) (*message.Response, error) { // Set up a listen channel to listen for the response (remove when done) lchan := s.NewListener(req.ID) @@ -203,13 +204,13 @@ func (p *Peer) Request(req message.Request, s stream.Stream) (*message.Response, } } -// Respond responds to a request from another peer -func (p *Peer) Respond(req *message.Request, s stream.Stream) { +// respond responds to a request from another peer +func (p *Peer) respond(req *message.Request, s stream.Stream) { response := message.Response{ID: req.ID} switch req.ResourceType { case message.ResourcePeerInfo: - response.Resource = p.subnet.Multiaddrs() + response.Resource = p.Subnet.Multiaddrs() break case message.ResourceBlock: case message.ResourceTransaction: @@ -230,6 +231,24 @@ func (p *Peer) Respond(req *message.Request, s stream.Stream) { } } +// handlePushMessage responds appropriately to the given push message received +// by this peer. +func (p *Peer) handlePushMessage(msg *message.Push) { + switch msg.ResourceType { + case message.ResourcePeerInfo: + mAddrs := msg.Resource.([]string) + for i := 0; i < len(mAddrs) && !p.Subnet.Full(); i++ { + p.Connect(mAddrs[i]) + } + break + case message.ResourceBlock: + case message.ResourceTransaction: + log.Warn("Push message handling cannot yet handle Blocka or Transaction resources") + default: + // Invalid resource type. + } +} + // NewMultiaddr creates a Multiaddress from the given Multiaddress (of the form // /ip4//tcp/) and the peer id (a hash) and turn them // into one Multiaddress. Will return error if Multiaddress is invalid. @@ -251,7 +270,7 @@ func (p *Peer) handleMessage(m message.Message, s stream.Stream) { switch m.Type() { case message.MessageRequest: // Respond to the request by sending request resource - p.Respond(m.(*message.Request), s) + p.respond(m.(*message.Request), s) break case message.MessageResponse: // Pass the response to the goroutine that requested it @@ -264,20 +283,20 @@ func (p *Peer) handleMessage(m message.Message, s stream.Stream) { break case message.MessagePush: // Handle data from push message - log.Error("Message push handling not yet implemented") + p.handlePushMessage(m.(*message.Push)) break default: // Invalid message type, ignore - log.Errorln("Received message with invalid type") + log.Debug("Received message with invalid type") } } // Listen listens for messages over the stream and responds to them, closing // the given stream and removing the remote peer from this peer's subnet when // done. This should be run as a goroutine. -func (p *Peer) Listen(sma string, s stream.Stream) { +func (p *Peer) listen(sma string, s stream.Stream) { defer s.Close() - defer p.subnet.RemovePeer(sma) + defer p.Subnet.RemovePeer(sma) for { err := s.SetDeadline(time.Now().Add(Timeout)) if err != nil { @@ -286,7 +305,7 @@ func (p *Peer) Listen(sma string, s stream.Stream) { } msg, err := message.Read(s) if err != nil { - log.WithError(err).Error("Error reading from the stream") + log.WithError(err).Error("Error reading from stream") return } log.Debug("Listener received message") diff --git a/peer/peer_test.go b/peer/peer_test.go index 61a8c08..171f5a4 100644 --- a/peer/peer_test.go +++ b/peer/peer_test.go @@ -193,18 +193,10 @@ func TestReceiveWithSubnetFull(t *testing.T) { t.FailNow() } - stream, err := lastPeer.Connect(ma.String()) + _, err = lastPeer.Connect(ma.String()) if err != nil { t.FailNow() } - - msg, err := message.Read(stream) - if err != nil || - msg.Type() != message.MessagePush || - msg.(*message.Push).ResourceType != message.ResourcePeerInfo { - t.FailNow() - } - } func TestRequest(t *testing.T) { From d740f1d0c184522a9c5f2d29f1d0d63c349277bc Mon Sep 17 00:00:00 2001 From: Bruno Bachmann Date: Sat, 3 Jun 2017 18:05:35 -0700 Subject: [PATCH 24/37] Minimal implementation of peer --- main.go | 53 ----- peer/peer.go | 437 ++++++++++++++---------------------------- peer/peer_test.go | 239 ----------------------- stream/stream.go | 50 ----- stream/stream_test.go | 76 -------- subnet/subnet.go | 85 -------- 6 files changed, 147 insertions(+), 793 deletions(-) delete mode 100644 peer/peer_test.go delete mode 100644 stream/stream.go delete mode 100644 stream/stream_test.go delete mode 100644 subnet/subnet.go diff --git a/main.go b/main.go index ef5e344..29f2c06 100644 --- a/main.go +++ b/main.go @@ -1,62 +1,9 @@ package main import ( - "flag" - log "github.com/Sirupsen/logrus" - "github.com/google/uuid" - "github.com/ubclaunchpad/cumulus/message" - "github.com/ubclaunchpad/cumulus/peer" ) func main() { log.Info("Starting Cumulus Peer") - - // Get and parse command line arguments - // targetPeer is a Multiaddr representing the target peer to connect to - // when joining the Cumulus Network. - // port is the port to communicate over (defaults to peer.DefaultPort). - // ip is the public IP address of the this host. - targetPeer := flag.String("t", "", "Target peer to connect to") - port := flag.Int("p", peer.DefaultPort, "TCP port to use for this host") - ip := flag.String("i", peer.DefaultIP, "IP address to use for this host") - debug := flag.Bool("d", false, "Enable debug logging") - flag.Parse() - - if *debug { - log.SetLevel(log.DebugLevel) - } - - // Set up a new host on the Cumulus network - host, err := peer.New(*ip, *port) - if err != nil { - log.Fatal(err) - } - - // Set the host StreamHandler for the Cumulus Protocol and use - // BasicStreamHandler as its StreamHandler. - host.SetStreamHandler(peer.CumulusProtocol, host.Receive) - if *targetPeer == "" { - // No target was specified, wait for incoming connections - log.Info("No target provided. Listening for incoming connections...") - select {} // Hang until someone connects to us - } - - stream, err := host.Connect(*targetPeer) - if err != nil { - log.Fatal(err) - } - - // Request peer info - request := message.Request{ - ID: uuid.New().String(), - ResourceType: message.ResourcePeerInfo, - } - _, err = host.Request(request, *stream) - if err != nil { - log.WithError(err).Error("Error writing message to stream") - return - } - - host.Close() } diff --git a/peer/peer.go b/peer/peer.go index 7559668..e1280b7 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -1,347 +1,204 @@ package peer import ( - "context" - "encoding/json" - "errors" - "fmt" + "net" + "sync" "time" log "github.com/Sirupsen/logrus" - "github.com/libp2p/go-libp2p-crypto" - "github.com/libp2p/go-libp2p-host" - net "github.com/libp2p/go-libp2p-net" - lpeer "github.com/libp2p/go-libp2p-peer" - "github.com/libp2p/go-libp2p-peerstore" - "github.com/libp2p/go-libp2p-swarm" - bhost "github.com/libp2p/go-libp2p/p2p/host/basic" - multiaddr "github.com/multiformats/go-multiaddr" + "github.com/google/uuid" "github.com/ubclaunchpad/cumulus/message" - "github.com/ubclaunchpad/cumulus/stream" - "github.com/ubclaunchpad/cumulus/subnet" ) const ( // DefaultPort is the TCP port hosts will communicate over if none is // provided - DefaultPort = 8765 - // CumulusProtocol is the name of the protocol peers communicate over - CumulusProtocol = "/cumulus/0.0.1" + DefaultPort = 8000 // DefaultIP is the IP address new hosts will use if none if provided DefaultIP = "127.0.0.1" // Timeout is the time after which reads from a stream will timeout Timeout = time.Second * 30 ) -// Peer is a cumulus Peer composed of a host -type Peer struct { - host.Host - Subnet subnet.Subnet +// Peerstore is a thread-safe container for all the peers we are currently +// connected to. +type Peerstore struct { + Peers map[string]*Peer + lock sync.RWMutex } -// New creates a Cumulus host with the given IP addr and TCP port. -// This may throw an error if we fail to create a key pair, a pid, or a new -// multiaddress. -func New(ip string, port int) (*Peer, error) { - // Generate a key pair for this host. We will only use the pudlic key to - // obtain a valid host ID. - // Cannot throw error with given arguments - priv, pub, _ := crypto.GenerateKeyPair(crypto.RSA, 2048) - - // Obtain Peer ID from public key. - // Cannot throw error with given argument - pid, _ := lpeer.IDFromPublicKey(pub) - - // Create a multiaddress (IP address and TCP port for this peer). - addr, err := multiaddr.NewMultiaddr(fmt.Sprintf("/ip4/%s/tcp/%d", ip, port)) - if err != nil { - return nil, err - } - - // Create Peerstore and add host's keys to it (avoids annoying err) - ps := peerstore.NewPeerstore() - ps.AddPubKey(pid, pub) - ps.AddPrivKey(pid, priv) - - // Create swarm (this is the interface to the libP2P Network) using the - // multiaddress, peerID, and peerStore we just created. - network, err := swarm.NewNetwork( - context.Background(), - []multiaddr.Multiaddr{addr}, - pid, - ps, - nil) - if err != nil { - return nil, err - } - - sn := *subnet.New(subnet.DefaultMaxPeers) - - // Actually create the host and peer with the network we just set up. - host := bhost.New(network) - peer := &Peer{ - Host: host, - Subnet: sn, - } - - // Build host multiaddress - hostAddr, _ := multiaddr.NewMultiaddr(fmt.Sprintf("/ipfs/%s", - host.ID().Pretty())) +// AddPeer synchronously adds the given peer to the peerstore +func (ps *Peerstore) AddPeer(p *Peer) { + ps.lock.Lock() + ps.Peers[p.ID.String()] = p + ps.lock.Unlock() +} - // Now we can build a full multiaddress to reach this host - // by encapsulating both addresses: - fullAddr := addr.Encapsulate(hostAddr) - log.Info("I am ", fullAddr) +// RemovePeer synchronously removes the given peer from the peerstore +func (ps *Peerstore) RemovePeer(p *Peer) { + ps.lock.Lock() + delete(ps.Peers, p.ID.String()) + ps.lock.Unlock() +} - // Add this host's address to its peerstore (avoid's net/identi error) - ps.AddAddr(pid, fullAddr, peerstore.PermanentAddrTTL) - return peer, nil +// Peer synchronously retreives the peer with the given id from the peerstore +func (ps *Peerstore) Peer(id string) *Peer { + ps.lock.RLock() + p := ps.Peers[id] + ps.lock.RUnlock() + return p } -// Receive is the function that gets called when a remote peer -// opens a new stream with this peer. -// This should be passed as the second argument to SetStreamHandler() after this -// peer is initialized. -func (p *Peer) Receive(s net.Stream) { - // Get remote peer's full multiaddress - ma, err := NewMultiaddr(s.Conn().RemoteMultiaddr(), s.Conn().RemotePeer()) - if err != nil { - log.WithError(err).Error("Failed to obtain valid remote peer multiaddress") - return - } +// Peer represents a remote peer we are connected to +type Peer struct { + ID uuid.UUID + Connection net.Conn + Peerstore *Peerstore + resChans map[string]chan *message.Response + reqChan chan *message.Request + pushChan chan *message.Push + lock sync.RWMutex +} - peerstream := *stream.New(s) +// Dispatch listens on this peer's Connection and passes received messages +// to the appropriate message handlers. +func (p *Peer) Dispatch() { + p.Connection.SetDeadline(time.Now().Add(Timeout)) - // Add the remote peer to this peer's subnet - err = p.Subnet.AddPeer(ma.String(), peerstream) - if err != nil { - // Subnet is full, advertise other available peers and then close - // the stream - log.WithError(err).Info("Peer subnet full. Advertising peers...") - msg := message.Push{ - ResourceType: message.ResourcePeerInfo, - Resource: p.Subnet.Multiaddrs(), - } - msgErr := msg.Write(s) - if msgErr != nil { - log.WithError(err).Error("Failed to send ResourcePeerInfo") + for { + msg, err := message.Read(p.Connection) + if err != nil { + log.WithError(err).Error("Dispatcher fialed to read message") + continue } - s.Close() - return + switch msg.Type() { + case message.MessageRequest: + p.reqChan <- msg.(*message.Request) + break + case message.MessageResponse: + id := msg.(*message.Response).ID + resChan := p.addResponseChan(id) + if resChan != nil { + resChan <- msg.(*message.Response) + } else { + log.Error("Dispatcher could not find channel for response %s", + msg.(*message.Response).ID) + } + p.removeResponseChan(id) + break + case message.MessagePush: + p.pushChan <- msg.(*message.Push) + break + default: + // Invalid messgae type. Ignore + log.Debug("Dispatcher received message with invalid type") + } } - go p.listen(ma.String(), peerstream) } -// Connect adds the given multiaddress to p's Peerstore and opens a stream -// with the peer at that multiaddress if the multiaddress is valid, otherwise -// returns error. This also spawns a listener that will receive and handle -// messages received over the stream with the peer we connected to. -func (p *Peer) Connect(sma string) (*stream.Stream, error) { - pid, targetAddr, err := extractPeerInfo(sma) - if err != nil { - return nil, err - } - - // Store the peer's address in this host's PeerStore - p.Peerstore().AddAddr(pid, targetAddr, peerstore.PermanentAddrTTL) - - // Open a stream with the peer - s, err := p.NewStream(context.Background(), pid, CumulusProtocol) - if err != nil { - return nil, err - } - err = s.SetDeadline(time.Now().Add(Timeout)) - if err != nil { - log.WithError(err).Error("Failed to set read deadline on stream") - return nil, err - } - - // Make a stream.Stream out of a net.Stream (sorry) - peerstream := *stream.New(s) - err = p.Subnet.AddPeer(sma, peerstream) - if err != nil { - s.Close() - return nil, err - } +// HandleRequests waits on this peer's request channel for incoming requests +// from the Dispatcher, responding to each request appropriately. +func (p *Peer) HandleRequests() { + var req *message.Request + for { + select { + case req = <-p.reqChan: + break + case <-time.After(Timeout): + continue + } - go p.listen(sma, peerstream) - return &peerstream, nil -} + res := message.Response{ID: req.ID} + + switch req.ResourceType { + case message.ResourcePeerInfo: + case message.ResourceBlock: + case message.ResourceTransaction: + res.Error = message.NewProtocolError(message.NotImplemented, + "PeerInfo, Block, and Transaction requests are not yet implemented on this peer") + default: + res.Error = message.NewProtocolError(message.InvalidResourceType, + "Invalid resource type") + } -// Broadcast sends message to all peers this peer is currently connected to -func (p *Peer) Broadcast(m message.Message) error { - var err error - for _, ma := range p.Subnet.Multiaddrs() { - err = m.Write(p.Subnet.Stream(ma)) + err := res.Write(p.Connection) if err != nil { - log.WithError(err).Error("Failed to send broadcast message to peer") + log.WithError(err).Error("RequestHandler failed to send response") } } - return err } -// Request sends a request to a remote peer over the given stream. -// Returns response if a response was received, otherwise returns error. -// You should typically run this function as a goroutine -func (p *Peer) Request(req message.Request, s stream.Stream) (*message.Response, error) { - // Set up a listen channel to listen for the response (remove when done) - lchan := s.NewListener(req.ID) - defer s.RemoveListener(req.ID) - - // Send request - err := req.Write(s) - if err != nil { - return nil, err - } +// HandlePushes waits on this peer's request channel for incoming requests +// from the Dispatcher, responding to each request appropriately. +func (p *Peer) HandlePushes() { + var push *message.Push + for { + select { + case push = <-p.pushChan: + break + case <-time.After(Timeout): + continue + } - // Receive response or timeout - select { - case res := <-lchan: - return res, nil - case <-time.After(Timeout): - return nil, errors.New("Timed out waiting for response") + switch push.ResourceType { + case message.ResourcePeerInfo: + case message.ResourceBlock: + case message.ResourceTransaction: + default: + // Invalid resource type. Ignore + } } } -// respond responds to a request from another peer -func (p *Peer) respond(req *message.Request, s stream.Stream) { - response := message.Response{ID: req.ID} - - switch req.ResourceType { - case message.ResourcePeerInfo: - response.Resource = p.Subnet.Multiaddrs() +// AwaitResponse waits on a response channel for a response message sent by the +// Dispatcher. When a response arrives it is handled appropriately. +func (p *Peer) AwaitResponse(req message.Request, c chan *message.Response) { + select { + case res := <-c: + // TODO: do something with the response + log.Debugf("Received response %s", res.ID) break - case message.ResourceBlock: - case message.ResourceTransaction: - response.Error = message.NewProtocolError(message.NotImplemented, - "Functionality not implemented on this peer") + case <-time.After(Timeout): break - default: - response.Error = message.NewProtocolError(message.InvalidResourceType, - "Invalid resource type") } - err := response.Write(s) - if err != nil { - log.WithError(err).Error("Failed to send reponse") - } else { - msgJSON, _ := json.Marshal(response) - log.Infof("Sending response: \n%s", string(msgJSON)) - } + p.removeResponseChan(req.ID) } -// handlePushMessage responds appropriately to the given push message received -// by this peer. -func (p *Peer) handlePushMessage(msg *message.Push) { - switch msg.ResourceType { - case message.ResourcePeerInfo: - mAddrs := msg.Resource.([]string) - for i := 0; i < len(mAddrs) && !p.Subnet.Full(); i++ { - p.Connect(mAddrs[i]) - } - break - case message.ResourceBlock: - case message.ResourceTransaction: - log.Warn("Push message handling cannot yet handle Blocka or Transaction resources") - default: - // Invalid resource type. +// Request sends the given request over this peer's Connection and spawns a +// response listener with AwaitResponse. Returns error if request could not be +// written. +func (p *Peer) Request(req message.Request) error { + resChan := p.addResponseChan(req.ID) + err := req.Write(p.Connection) + if err != nil { + p.removeResponseChan(req.ID) + return err } -} -// NewMultiaddr creates a Multiaddress from the given Multiaddress (of the form -// /ip4//tcp/) and the peer id (a hash) and turn them -// into one Multiaddress. Will return error if Multiaddress is invalid. -func NewMultiaddr(iAddr multiaddr.Multiaddr, pid lpeer.ID) (multiaddr.Multiaddr, error) { - strAddr := iAddr.String() - strID := pid.Pretty() - strMA := fmt.Sprintf("%s/ipfs/%s", strAddr, strID) - mAddr, err := multiaddr.NewMultiaddr(strMA) - return mAddr, err + go p.AwaitResponse(req, resChan) + return nil } -// HandleMessage responds to a received message -func (p *Peer) handleMessage(m message.Message, s stream.Stream) { - msgJSON, err := json.Marshal(m) - if err == nil { - log.Infof("Received message: \n%s", string(msgJSON)) - } - - switch m.Type() { - case message.MessageRequest: - // Respond to the request by sending request resource - p.respond(m.(*message.Request), s) - break - case message.MessageResponse: - // Pass the response to the goroutine that requested it - res := m.(*message.Response) - lchan := s.Listener(res.ID) - if lchan != nil { - log.Debug("Found listener channel for response") - lchan <- res - } - break - case message.MessagePush: - // Handle data from push message - p.handlePushMessage(m.(*message.Push)) - break - default: - // Invalid message type, ignore - log.Debug("Received message with invalid type") - } +func (p *Peer) addResponseChan(id string) chan *message.Response { + resChan := make(chan *message.Response) + p.lock.Lock() + p.resChans[id] = resChan + p.lock.Unlock() + return resChan } -// Listen listens for messages over the stream and responds to them, closing -// the given stream and removing the remote peer from this peer's subnet when -// done. This should be run as a goroutine. -func (p *Peer) listen(sma string, s stream.Stream) { - defer s.Close() - defer p.Subnet.RemovePeer(sma) - for { - err := s.SetDeadline(time.Now().Add(Timeout)) - if err != nil { - log.WithError(err).Error("Failed to set read deadline on stream") - return - } - msg, err := message.Read(s) - if err != nil { - log.WithError(err).Error("Error reading from stream") - return - } - log.Debug("Listener received message") - go p.handleMessage(msg, s) - } +func (p *Peer) removeResponseChan(id string) { + p.lock.Lock() + delete(p.resChans, id) + p.lock.Unlock() } -// ExtractPeerInfo extracts the peer ID and multiaddress from the -// given multiaddress. -// Returns peer ID (esentially 46 character hash created by the peer) -// and the peer's multiaddress in the form /ip4//tcp/. -func extractPeerInfo(sma string) (lpeer.ID, multiaddr.Multiaddr, error) { - log.Debug("Extracting peer info from ", sma) - - ipfsaddr, err := multiaddr.NewMultiaddr(sma) - if err != nil { - return "-", nil, err - } - - // Cannot throw error when passed P_IPFS - pid, err := ipfsaddr.ValueForProtocol(multiaddr.P_IPFS) - if err != nil { - return "-", nil, err - } - - // Cannot return error if no error was returned in ValueForProtocol - peerid, _ := lpeer.IDB58Decode(pid) - - // Decapsulate the /ipfs/ part from the target - // /ip4//ipfs/ becomes /ip4/ - targetPeerAddr, err := multiaddr.NewMultiaddr( - fmt.Sprintf("/ipfs/%s", lpeer.IDB58Encode(peerid))) - if err != nil { - return "-", nil, err - } - - trgtAddr := ipfsaddr.Decapsulate(targetPeerAddr) - return peerid, trgtAddr, nil +func (p *Peer) responseChan(id string) chan *message.Response { + var resChan chan *message.Response + p.lock.RLock() + resChan = p.resChans[id] + p.lock.RUnlock() + return resChan } diff --git a/peer/peer_test.go b/peer/peer_test.go deleted file mode 100644 index 171f5a4..0000000 --- a/peer/peer_test.go +++ /dev/null @@ -1,239 +0,0 @@ -package peer - -import ( - "fmt" - "testing" - - log "github.com/Sirupsen/logrus" - "github.com/google/uuid" - "github.com/ubclaunchpad/cumulus/message" - "github.com/ubclaunchpad/cumulus/subnet" -) - -func TestMain(t *testing.T) { - // Disable logging for tests - log.SetLevel(log.FatalLevel) -} - -func TestNewDefault(t *testing.T) { - h, err := New(DefaultIP, DefaultPort) - if err != nil { - t.Fail() - } - - if h == nil { - t.Fail() - } - - if h.Peerstore() == nil { - t.Fail() - } -} - -func TestNewValidPort(t *testing.T) { - h, err := New(DefaultIP, 8000) - if err != nil { - t.Fail() - } - - if h == nil { - t.Fail() - } - - if h.Peerstore() == nil { - t.Fail() - } -} - -func TestNewValidIP(t *testing.T) { - _, err := New("123.211.231.45", DefaultPort) - if err == nil { - t.Fail() - } -} - -func TestNewInvalidIP(t *testing.T) { - _, err := New("asdfasdf", 123) - if err == nil { - t.Fail() - } -} - -func TestExtractPeerInfoValidMultiAddr(t *testing.T) { - peerma := "/ip4/127.0.0.1/tcp/8765/ipfs/QmQdfp9Ug4MoLRsBToDPN2aQhg2jPtmmA8UidQUTXGjZcy" - pid, ma, err := extractPeerInfo(peerma) - - if err != nil { - t.Fail() - } - - if pid.Pretty() != "QmQdfp9Ug4MoLRsBToDPN2aQhg2jPtmmA8UidQUTXGjZcy" { - t.Fail() - } - - if ma.String() != "/ip4/127.0.0.1/tcp/8765" { - t.Fail() - } -} - -func TestExtractPeerInfoInvalidIP(t *testing.T) { - peerma := "/ip4/203.532.211.5/tcp/8765/ipfs/Qmb89FuJ8UG3dpgUqEYu9eUqK474uP3mx32WnQ7kePXp8N" - _, _, err := extractPeerInfo(peerma) - - if err == nil { - t.Fail() - } -} - -func TestReceiveValidMessage(t *testing.T) { - sender, err := New(DefaultIP, DefaultPort) - if err != nil { - t.FailNow() - } - - sender.SetStreamHandler(CumulusProtocol, sender.Receive) - - receiver, err := New(DefaultIP, 8080) - if err != nil { - t.FailNow() - } - - receiver.SetStreamHandler(CumulusProtocol, receiver.Receive) - - receiverMultiAddr := fmt.Sprintf("%s/ipfs/%s", - receiver.Addrs()[0], receiver.ID().Pretty()) - - stream, err := sender.Connect(receiverMultiAddr) - if err != nil { - t.FailNow() - } - - _, err = stream.Write([]byte("This is a test\n")) - if err != nil { - t.FailNow() - } -} - -func TestReceiveInvalidAddress(t *testing.T) { - receiver, err := New(DefaultIP, DefaultPort) - if err != nil { - t.Fail() - } - - sender, err := New(DefaultIP, 8080) - if err != nil { - t.Fail() - } - - receiver.SetStreamHandler(CumulusProtocol, receiver.Receive) - - _, err = sender.Connect(receiver.Addrs()[0].String()) - if err == nil { - t.Fail() - } -} - -func TestConnectWithSubnetFull(t *testing.T) { - testPeer, err := New(DefaultIP, DefaultPort) - if err != nil { - t.Fail() - } - peers := make([]*Peer, subnet.DefaultMaxPeers) - - for i := 0; i < subnet.DefaultMaxPeers; i++ { - peers[i], err = New(DefaultIP, 8080+i) - if err != nil { - t.FailNow() - } - peers[i].SetStreamHandler(CumulusProtocol, peers[i].Receive) - ma, maErr := NewMultiaddr(peers[i].Addrs()[0], peers[i].ID()) - if maErr != nil { - t.FailNow() - } - _, err = testPeer.Connect(ma.String()) - if err != nil { - t.FailNow() - } - } - - lastPeer, err := New(DefaultIP, 8081+subnet.DefaultMaxPeers) - if err != nil { - t.FailNow() - } - _, err = testPeer.Connect(lastPeer.Addrs()[0].String()) - if err == nil { - t.FailNow() - } -} - -func TestReceiveWithSubnetFull(t *testing.T) { - targetPeer, err := New(DefaultIP, DefaultPort) - if err != nil { - t.FailNow() - } - targetPeer.SetStreamHandler(CumulusProtocol, targetPeer.Receive) - ma, err := NewMultiaddr(targetPeer.Addrs()[0], targetPeer.ID()) - if err != nil { - t.FailNow() - } - - for i := 0; i < subnet.DefaultMaxPeers; i++ { - p, err := New(DefaultIP, 8080+i) - if err != nil { - t.FailNow() - } - _, err = p.Connect(ma.String()) - if err != nil { - t.FailNow() - } - } - - lastPeer, err := New(DefaultIP, 8080+subnet.DefaultMaxPeers) - if err != nil { - t.FailNow() - } - - _, err = lastPeer.Connect(ma.String()) - if err != nil { - t.FailNow() - } -} - -func TestRequest(t *testing.T) { - requester, err := New(DefaultIP, DefaultPort) - if err != nil { - t.Fail() - } - - responder, err := New(DefaultIP, 8080) - if err != nil { - t.Fail() - } - - requester.SetStreamHandler(CumulusProtocol, requester.Receive) - responder.SetStreamHandler(CumulusProtocol, responder.Receive) - responderAddr, err := NewMultiaddr(responder.Addrs()[0], responder.ID()) - if err != nil { - t.Fail() - } - - stream, err := requester.Connect(responderAddr.String()) - if err != nil { - fmt.Println("Failed to connect to remote peer") - t.Fail() - } - - request := message.Request{ - ID: uuid.New().String(), - ResourceType: message.ResourcePeerInfo, - Params: nil, - } - response, err := requester.Request(request, *stream) - if err != nil { - fmt.Printf("Failed to make request: %s", err) - t.FailNow() - } else if response.Error != nil { - fmt.Printf("Remote peer returned response %s", response.Error) - t.FailNow() - } -} diff --git a/stream/stream.go b/stream/stream.go deleted file mode 100644 index 885d4d0..0000000 --- a/stream/stream.go +++ /dev/null @@ -1,50 +0,0 @@ -package stream - -import ( - "sync" - - "github.com/libp2p/go-libp2p-net" - "github.com/ubclaunchpad/cumulus/message" -) - -// Stream is a synchronized container for net.Stream and a mapping of listener -// ids to response channels -type Stream struct { - net.Stream - listeners map[string]chan *message.Response - lock *sync.RWMutex -} - -// New returns a new Stream containing the given net.Stream -func New(s net.Stream) *Stream { - return &Stream{ - s, - make(map[string]chan *message.Response), - &sync.RWMutex{}, - } -} - -// NewListener synchronously adds a listener to this peer's listeners map -func (s *Stream) NewListener(id string) chan *message.Response { - s.lock.Lock() - s.listeners[id] = make(chan *message.Response) - lchan := s.listeners[id] - s.lock.Unlock() - return lchan -} - -// RemoveListener synchronously removes a listener from this peer's listeners map -func (s *Stream) RemoveListener(id string) { - s.lock.Lock() - delete(s.listeners, id) - s.lock.Unlock() -} - -// Listener synchronously retrieves the channel the listener with the given -// request/response id is waiting on -func (s *Stream) Listener(id string) chan *message.Response { - s.lock.RLock() - lchan := s.listeners[id] - s.lock.RUnlock() - return lchan -} diff --git a/stream/stream_test.go b/stream/stream_test.go deleted file mode 100644 index 2635fe8..0000000 --- a/stream/stream_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package stream - -import ( - "testing" - "time" - - "github.com/google/uuid" - "github.com/ubclaunchpad/cumulus/message" -) - -func TestNewListener(t *testing.T) { - stream := New(nil) - resource := map[string]int{ - "hello": 1, - "world": 500, - } - response := &message.Response{ - ID: uuid.New().String(), - Error: message.NewProtocolError(message.InvalidResourceType, "Invalid resource type"), - Resource: resource, - } - lchan := stream.NewListener(response.ID) - - go func(lchan chan *message.Response, response *message.Response) { - var received *message.Response - select { - case received = <-lchan: - break - case <-time.After(time.Second * 3): - t.FailNow() - } - if received.ID != response.ID || received.Error != response.Error { - t.FailNow() - } - if received.Resource.(map[string]int)["hello"] != 1 || - received.Resource.(map[string]int)["world"] != 500 { - t.FailNow() - } - }(lchan, response) - - go func(lchan chan *message.Response, response *message.Response) { - lchan <- response - }(lchan, response) -} - -func TestAsyncListeners(t *testing.T) { - stream := New(nil) - numlisteners := 10 - - testRoutine := func(stream *Stream, numlisteners int) { - ids := make([]string, numlisteners) - lchans := make([]chan *message.Response, numlisteners) - for i := 0; i < numlisteners; i++ { - ids[i] = uuid.New().String() - lchans[i] = stream.NewListener(ids[i]) - } - for i := 0; i < numlisteners; i++ { - lchan := stream.Listener(ids[i]) - if lchan != lchans[i] { - t.FailNow() - } - } - for i := 0; i < numlisteners; i++ { - stream.RemoveListener(ids[i]) - lchan := stream.Listener(ids[i]) - if lchan != nil { - t.FailNow() - } - } - } - - go testRoutine(stream, numlisteners) - go testRoutine(stream, numlisteners) - go testRoutine(stream, numlisteners) - go testRoutine(stream, numlisteners) -} diff --git a/subnet/subnet.go b/subnet/subnet.go deleted file mode 100644 index 6f5803d..0000000 --- a/subnet/subnet.go +++ /dev/null @@ -1,85 +0,0 @@ -package subnet - -import ( - "errors" - "sync" - - log "github.com/Sirupsen/logrus" - "github.com/ubclaunchpad/cumulus/stream" -) - -const ( - // DefaultMaxPeers is the maximum number of peers a peer can be in - // communication with at any given time. - DefaultMaxPeers = 6 -) - -// Subnet represents the set of peers a peer is connected to at any given time -type Subnet struct { - peers map[string]*stream.Stream - maxPeers int - lock sync.RWMutex -} - -// New returns a pointer to an empty Subnet with the given maxPeers. -func New(maxPeers int) *Subnet { - p := make(map[string]*stream.Stream) - sn := Subnet{maxPeers: maxPeers, peers: p, lock: sync.RWMutex{}} - return &sn -} - -// AddPeer adds a peer's multiaddress and the corresponding stream between the -// local peer and the remote peer to the subnet. If the subnet is already -// full, or the multiaddress is not valid, returns error. -func (sn *Subnet) AddPeer(sma string, stream stream.Stream) error { - sn.lock.Lock() - if sn.Full() { - sn.lock.Unlock() - return errors.New("Cannot insert new mapping, Subnet is already full") - } - - // Check if it's already in this subnet - if sn.peers[sma] != nil { - log.Debugf("Peer %s is already in subnet", sma) - } else { - log.Debugf("Adding peer %s to subnet", sma) - sn.peers[sma] = &stream - } - sn.lock.Unlock() - return nil -} - -// RemovePeer removes the mapping with the key mAddr from the subnet if it -// exists. -func (sn *Subnet) RemovePeer(sma string) { - sn.lock.Lock() - log.Debugf("Removing peer %s from subnet", sma) - delete(sn.peers, sma) - sn.lock.Unlock() -} - -// Stream returns the stream associated with the given multiaddr in this subnet. -// Returns nil if the multiaddr is not in this subnet. -func (sn *Subnet) Stream(sma string) stream.Stream { - sn.lock.RLock() - stream := *sn.peers[sma] - sn.lock.RUnlock() - return stream -} - -// Full returns true if the number of peers in the sunbet is at or over the -// limit set for that subnet, otherwise returns false. -func (sn *Subnet) Full() bool { - return len(sn.peers) >= sn.maxPeers -} - -// Multiaddrs returns a list of all multiaddresses contined in this subnet -func (sn *Subnet) Multiaddrs() []string { - sn.lock.RLock() - mas := make([]string, 0, len(sn.peers)) - for sma := range sn.peers { - mas = append(mas, sma) - } - sn.lock.RUnlock() - return mas -} From ad7a90b22c2ebe6c3706f27a243b6082663d897f Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Sun, 4 Jun 2017 22:05:07 -0700 Subject: [PATCH 25/37] refactoring --- cmd/run.go | 8 +-- peer/peer.go | 144 +++++++++++++++++++++++++++------------------------ 2 files changed, 81 insertions(+), 71 deletions(-) diff --git a/cmd/run.go b/cmd/run.go index aa105f4..0099d0d 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -25,7 +25,7 @@ var runCmd = &cobra.Command{ }, } -var ps = &peer.Peerstore{Peers: make(map[string]*peer.Peer)} +var ps = peer.NewPeerStore() func init() { RootCmd.AddCommand(runCmd) @@ -59,9 +59,9 @@ func run(port int, ip, target string, verbose bool) { func handleConnection(c net.Conn) { p := peer.New(c, ps) - ps.AddPeer(p) + ps.Add(p) go p.Dispatch() - go p.HandlePushes() - go p.HandleRequests() + go p.PushHandler() + go p.RequestHandler() } diff --git a/peer/peer.go b/peer/peer.go index f16fa31..51a3a69 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -20,53 +20,92 @@ const ( Timeout = time.Second * 30 ) -// Peerstore is a thread-safe container for all the peers we are currently +// PeerStore is a thread-safe container for all the peers we are currently // connected to. -type Peerstore struct { - Peers map[string]*Peer +type PeerStore struct { + peers map[string]*Peer lock sync.RWMutex } -// AddPeer synchronously adds the given peer to the peerstore -func (ps *Peerstore) AddPeer(p *Peer) { +// NewPeerStore returns an initialized peerstore. +func NewPeerStore() *PeerStore { + return &PeerStore{ + peers: make(map[string]*Peer), + lock: sync.RWMutex{}, + } +} + +// Add synchronously adds the given peer to the peerstore +func (ps *PeerStore) Add(p *Peer) { ps.lock.Lock() - ps.Peers[p.ID.String()] = p - ps.lock.Unlock() + defer ps.lock.Unlock() + ps.peers[p.ID.String()] = p } -// RemovePeer synchronously removes the given peer from the peerstore -func (ps *Peerstore) RemovePeer(p *Peer) { +// Remove synchronously removes the given peer from the peerstore +func (ps *PeerStore) Remove(p *Peer) { ps.lock.Lock() - delete(ps.Peers, p.ID.String()) - ps.lock.Unlock() + defer ps.lock.Unlock() + delete(ps.peers, p.ID.String()) } -// Peer synchronously retreives the peer with the given id from the peerstore -func (ps *Peerstore) Peer(id string) *Peer { +// Get synchronously retrieves the peer with the given id from the peerstore +func (ps *PeerStore) Get(id string) *Peer { ps.lock.RLock() - p := ps.Peers[id] - ps.lock.RUnlock() + defer ps.lock.RUnlock() + p := ps.peers[id] return p } -// Peer represents a remote peer we are connected to +// ChanStore is a threadsafe container for response channels. +type ChanStore struct { + chans map[string]chan *message.Response + lock sync.RWMutex +} + +// Add synchronously adds a channel with the given id to the store. +func (cs *ChanStore) Add(id string, channel chan *message.Response) { + cs.lock.Lock() + defer cs.lock.Lock() + cs.chans[id] = channel +} + +// Remove synchronously removes the channel with the given ID. +func (cs *ChanStore) Remove(id string) { + cs.lock.Lock() + defer cs.lock.Unlock() + delete(cs.chans, id) +} + +// Get retrieves the channel with the given ID. +func (cs *ChanStore) Get(id string) chan *message.Response { + cs.lock.RLock() + defer cs.lock.RUnlock() + return cs.chans[id] +} + +// Peer represents a remote peer we are connected to. type Peer struct { ID uuid.UUID Connection net.Conn - Peerstore *Peerstore - resChans map[string]chan *message.Response + Store *PeerStore + resChans *ChanStore reqChan chan *message.Request pushChan chan *message.Push lock sync.RWMutex } // New returns a new Peer -func New(c net.Conn, ps *Peerstore) *Peer { +func New(c net.Conn, ps *PeerStore) *Peer { + cs := &ChanStore{ + chans: make(map[string]chan *message.Response), + lock: sync.RWMutex{}, + } return &Peer{ ID: uuid.New(), Connection: c, - Peerstore: ps, - resChans: make(map[string]chan *message.Response), + Store: ps, + resChans: cs, reqChan: make(chan *message.Request), pushChan: make(chan *message.Push), } @@ -80,28 +119,24 @@ func (p *Peer) Dispatch() { for { msg, err := message.Read(p.Connection) if err != nil { - log.WithError(err).Error("Dispatcher fialed to read message") + log.WithError(err).Error("Dispatcher failed to read message") continue } switch msg.Type() { case message.MessageRequest: p.reqChan <- msg.(*message.Request) - break case message.MessageResponse: - id := msg.(*message.Response).ID - resChan := p.addResponseChan(id) + res := msg.(*message.Response) + resChan := p.resChans.Get(res.ID) if resChan != nil { resChan <- msg.(*message.Response) } else { - log.Error("Dispatcher could not find channel for response %s", - msg.(*message.Response).ID) + log.Errorf("Dispatcher could not find channel for response %s", res.ID) } - p.removeResponseChan(id) - break + p.resChans.Remove(res.ID) case message.MessagePush: p.pushChan <- msg.(*message.Push) - break default: // Invalid messgae type. Ignore log.Debug("Dispatcher received message with invalid type") @@ -109,9 +144,9 @@ func (p *Peer) Dispatch() { } } -// HandleRequests waits on this peer's request channel for incoming requests +// RequestHandler waits on this peer's request channel for incoming requests // from the Dispatcher, responding to each request appropriately. -func (p *Peer) HandleRequests() { +func (p *Peer) RequestHandler() { var req *message.Request for { select { @@ -124,9 +159,7 @@ func (p *Peer) HandleRequests() { res := message.Response{ID: req.ID} switch req.ResourceType { - case message.ResourcePeerInfo: - case message.ResourceBlock: - case message.ResourceTransaction: + case message.ResourcePeerInfo, message.ResourceBlock, message.ResourceTransaction: res.Error = message.NewProtocolError(message.NotImplemented, "PeerInfo, Block, and Transaction requests are not yet implemented on this peer") default: @@ -141,9 +174,9 @@ func (p *Peer) HandleRequests() { } } -// HandlePushes waits on this peer's request channel for incoming requests +// PushHandler waits on this peer's request channel for incoming requests // from the Dispatcher, responding to each request appropriately. -func (p *Peer) HandlePushes() { +func (p *Peer) PushHandler() { var push *message.Push for { select { @@ -163,54 +196,31 @@ func (p *Peer) HandlePushes() { } } -// AwaitResponse waits on a response channel for a response message sent by the +// HandleResponse waits on a response channel for a response message sent by the // Dispatcher. When a response arrives it is handled appropriately. -func (p *Peer) AwaitResponse(req message.Request, c chan *message.Response) { +func (p *Peer) HandleResponse(req message.Request, c chan *message.Response) { + defer p.resChans.Remove(req.ID) select { case res := <-c: // TODO: do something with the response log.Debugf("Received response %s", res.ID) - break case <-time.After(Timeout): break } - - p.removeResponseChan(req.ID) } // Request sends the given request over this peer's Connection and spawns a // response listener with AwaitResponse. Returns error if request could not be // written. func (p *Peer) Request(req message.Request) error { - resChan := p.addResponseChan(req.ID) + resChan := make(chan *message.Response) + p.resChans.Add(req.ID, resChan) err := req.Write(p.Connection) if err != nil { - p.removeResponseChan(req.ID) + p.resChans.Remove(req.ID) return err } - go p.AwaitResponse(req, resChan) + go p.HandleResponse(req, resChan) return nil } - -func (p *Peer) addResponseChan(id string) chan *message.Response { - resChan := make(chan *message.Response) - p.lock.Lock() - p.resChans[id] = resChan - p.lock.Unlock() - return resChan -} - -func (p *Peer) removeResponseChan(id string) { - p.lock.Lock() - delete(p.resChans, id) - p.lock.Unlock() -} - -func (p *Peer) responseChan(id string) chan *message.Response { - var resChan chan *message.Response - p.lock.RLock() - resChan = p.resChans[id] - p.lock.RUnlock() - return resChan -} From 1640626135087ab2ecedff66033a7762665a3e10 Mon Sep 17 00:00:00 2001 From: Bruno Bachmann Date: Mon, 5 Jun 2017 21:55:56 -0700 Subject: [PATCH 26/37] Refactor handleConnection() callback, update Peer and Peerstore --- cmd/run.go | 14 +---------- conn/conn.go | 4 +++- peer/peer.go | 67 +++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 63 insertions(+), 22 deletions(-) diff --git a/cmd/run.go b/cmd/run.go index aa105f4..78a5b44 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -2,7 +2,6 @@ package cmd import ( "fmt" - "net" log "github.com/Sirupsen/logrus" "github.com/spf13/cobra" @@ -25,8 +24,6 @@ var runCmd = &cobra.Command{ }, } -var ps = &peer.Peerstore{Peers: make(map[string]*peer.Peer)} - func init() { RootCmd.AddCommand(runCmd) @@ -51,17 +48,8 @@ func run(port int, ip, target string, verbose bool) { log.SetLevel(log.DebugLevel) } - err := conn.Listen(fmt.Sprintf("%s:%d", ip, port), handleConnection) + err := conn.Listen(fmt.Sprintf("%s:%d", ip, port), peer.HandleConnection) if err != nil { log.WithError(err).Errorf("Failed to listen on %s:%d", ip, port) } } - -func handleConnection(c net.Conn) { - p := peer.New(c, ps) - ps.AddPeer(p) - - go p.Dispatch() - go p.HandlePushes() - go p.HandleRequests() -} diff --git a/conn/conn.go b/conn/conn.go index c6d7975..f0f182f 100644 --- a/conn/conn.go +++ b/conn/conn.go @@ -1,6 +1,8 @@ package conn -import "net" +import ( + "net" +) // Dial opens a connection to a remote host. `host` should be a string // in the format |: diff --git a/peer/peer.go b/peer/peer.go index f16fa31..23fa5f5 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -7,6 +7,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/google/uuid" + "github.com/ubclaunchpad/cumulus/conn" "github.com/ubclaunchpad/cumulus/message" ) @@ -20,33 +21,58 @@ const ( Timeout = time.Second * 30 ) +// PStore stores information about every peer we are connected to. +var PStore = &Peerstore{Peers: make([]*Peer, 0)} + // Peerstore is a thread-safe container for all the peers we are currently // connected to. type Peerstore struct { - Peers map[string]*Peer + Peers []*Peer lock sync.RWMutex } // AddPeer synchronously adds the given peer to the peerstore func (ps *Peerstore) AddPeer(p *Peer) { ps.lock.Lock() - ps.Peers[p.ID.String()] = p + ps.Peers = append(ps.Peers, p) ps.lock.Unlock() } // RemovePeer synchronously removes the given peer from the peerstore -func (ps *Peerstore) RemovePeer(p *Peer) { +func (ps *Peerstore) RemovePeer(id string) { ps.lock.Lock() - delete(ps.Peers, p.ID.String()) + for i := 0; i < len(ps.Peers); i++ { + if ps.Peers[i].ID.String() == id { + ps.Peers = append(ps.Peers[:i], ps.Peers[i+1:]...) + } + } ps.lock.Unlock() } // Peer synchronously retreives the peer with the given id from the peerstore func (ps *Peerstore) Peer(id string) *Peer { + var peer *Peer + ps.lock.RLock() + for i := 0; i < len(ps.Peers); i++ { + if ps.Peers[i].ID.String() == id { + peer = ps.Peers[i] + break + } + } + ps.lock.RUnlock() + return peer +} + +// Addrs returns the list of addresses of the peers in the peerstore in the form +// : +func (ps *Peerstore) Addrs() []string { ps.lock.RLock() - p := ps.Peers[id] + addrs := make([]string, len(ps.Peers), len(ps.Peers)) + for i := 0; i < len(ps.Peers); i++ { + addrs[i] = ps.Peers[i].Connection.RemoteAddr().String() + } ps.lock.RUnlock() - return p + return addrs } // Peer represents a remote peer we are connected to @@ -72,6 +98,18 @@ func New(c net.Conn, ps *Peerstore) *Peer { } } +// HandleConnection is called when a new connection is opened with us by a +// remote peer. It will create a dispatcher and message handlers to handle +// sending and reveiving messages over the new connection. +func HandleConnection(c net.Conn) { + p := New(c, PStore) + PStore.AddPeer(p) + + go p.Dispatch() + go p.HandlePushes() + go p.HandleRequests() +} + // Dispatch listens on this peer's Connection and passes received messages // to the appropriate message handlers. func (p *Peer) Dispatch() { @@ -94,7 +132,7 @@ func (p *Peer) Dispatch() { if resChan != nil { resChan <- msg.(*message.Response) } else { - log.Error("Dispatcher could not find channel for response %s", + log.Errorf("Dispatcher could not find channel for response %s", msg.(*message.Response).ID) } p.removeResponseChan(id) @@ -125,10 +163,12 @@ func (p *Peer) HandleRequests() { switch req.ResourceType { case message.ResourcePeerInfo: + res.Resource = p.Peerstore.Addrs() + break case message.ResourceBlock: case message.ResourceTransaction: res.Error = message.NewProtocolError(message.NotImplemented, - "PeerInfo, Block, and Transaction requests are not yet implemented on this peer") + "Block and Transaction requests are not yet implemented on this peer") default: res.Error = message.NewProtocolError(message.InvalidResourceType, "Invalid resource type") @@ -155,6 +195,17 @@ func (p *Peer) HandlePushes() { switch push.ResourceType { case message.ResourcePeerInfo: + for _, addr := range push.Resource.([]string) { + conn.Listen(addr, func(c net.Conn) { + p := New(c, PStore) + PStore.AddPeer(p) + + go p.Dispatch() + go p.HandlePushes() + go p.HandleRequests() + }) + } + break case message.ResourceBlock: case message.ResourceTransaction: default: From 634b32334f121e039b8b6952a277d8538772b28a Mon Sep 17 00:00:00 2001 From: Bruno Bachmann Date: Mon, 5 Jun 2017 22:29:14 -0700 Subject: [PATCH 27/37] Rename HandleConnection to ConnectionHandler --- peer/peer.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/peer/peer.go b/peer/peer.go index 3fc5b30..3648f9f 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -127,10 +127,10 @@ func New(c net.Conn, ps *PeerStore) *Peer { } } -// HandleConnection is called when a new connection is opened with us by a +// ConnectionHandler is called when a new connection is opened with us by a // remote peer. It will create a dispatcher and message handlers to handle // sending and reveiving messages over the new connection. -func HandleConnection(c net.Conn) { +func ConnectionHandler(c net.Conn) { p := New(c, PStore) PStore.Add(p) @@ -238,9 +238,9 @@ func (p *Peer) PushHandler() { } } -// HandleResponse waits on a response channel for a response message sent by the +// AwaitResponse waits on a response channel for a response message sent by the // Dispatcher. When a response arrives it is handled appropriately. -func (p *Peer) HandleResponse(req message.Request, c chan *message.Response) { +func (p *Peer) AwaitResponse(req message.Request, c chan *message.Response) { defer p.resChans.Remove(req.ID) select { case res := <-c: @@ -263,6 +263,6 @@ func (p *Peer) Request(req message.Request) error { return err } - go p.HandleResponse(req, resChan) + go p.AwaitResponse(req, resChan) return nil } From b2bb416ed296c50f831e997dbf923ed1892f4d74 Mon Sep 17 00:00:00 2001 From: Bruno Bachmann Date: Tue, 6 Jun 2017 22:48:44 -0700 Subject: [PATCH 28/37] Update `run.go`, update glide config, fix peer bugs, --- cmd/run.go | 25 ++++++- glide.lock | 201 ++------------------------------------------------- glide.yaml | 10 --- peer/peer.go | 20 +++-- 4 files changed, 40 insertions(+), 216 deletions(-) diff --git a/cmd/run.go b/cmd/run.go index 78a5b44..428db18 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -48,8 +48,27 @@ func run(port int, ip, target string, verbose bool) { log.SetLevel(log.DebugLevel) } - err := conn.Listen(fmt.Sprintf("%s:%d", ip, port), peer.HandleConnection) - if err != nil { - log.WithError(err).Errorf("Failed to listen on %s:%d", ip, port) + // Set up listener. This is run as a goroutine because Listen blocks forever + log.Infof("Starting listener on %s:%d", ip, port) + go func() { + err := conn.Listen(fmt.Sprintf("%s:%d", ip, port), peer.ConnectionHandler) + if err != nil { + log.WithError(err).Fatalf("Failed to listen on %s:%d", ip, port) + } + }() + + // Connect to remote peer if target provided + if target != "" { + log.Infof("Dialing target %s", target) + c, err := conn.Dial(target) + if err != nil { + log.WithError(err).Errorf("Failed to dial target %s", target) + return + } + peer.ConnectionHandler(c) } + + // Hang forever. All the work from here on is handled in goroutines. We need + // this to hang to keep them alive. + select {} } diff --git a/glide.lock b/glide.lock index bfb9dce..f3b4b0f 100644 --- a/glide.lock +++ b/glide.lock @@ -1,38 +1,10 @@ -hash: 416d5c866bf090f534cd568c92cf41d6cedb8ac5245a33e4074000ce61e72f3b -updated: 2017-06-03T10:30:04.32905091-07:00 +hash: 30b0423a31fe8502a34437bed1a6eafa91c8f0ff490461948e0cbb112c5fa11b +updated: 2017-06-06T22:20:02.631081911-07:00 imports: -- name: github.com/agl/ed25519 - version: 5312a61534124124185d41f09206b9fef1d88403 - subpackages: - - extra25519 - - edwards25519 -- name: github.com/btcsuite/btcd - version: 1ae306021e323ae11c71ffb8546fbd9019e6cb6f - subpackages: - - btcec -- name: github.com/coreos/go-semver - version: 5e3acbb5668c4c3deb4842615c4098eb61fb6b1e - subpackages: - - semver -- name: github.com/docker/spdystream - version: ed496381df8283605c435b86d4fdd6f4f20b8c6e - subpackages: - - spdy -- name: github.com/fd/go-nat - version: dcaf50131e4810440bed2cbb6f7f32c4f4cc95dd - name: github.com/fsnotify/fsnotify version: 4da3e2cfbabc9f751898f250b49f2439785783a1 -- name: github.com/gogo/protobuf - version: 30433562cfbf487fe1df7cd26c7bab168d2f14d0 - subpackages: - - proto - - io - name: github.com/google/uuid version: 064e2069ce9c359c118179501254f67d7d37ba24 -- name: github.com/gxed/eventfd - version: 80a92cca79a8041496ccc9dd773fcb52a57ec6f9 -- name: github.com/gxed/GoEndian - version: 0f5c6873267e5abf306ffcdfcfa4bf77517ef4a7 - name: github.com/hashicorp/hcl version: 392dba7d905ed5d04a5794ba89f558b27e2ba1ca subpackages: @@ -44,126 +16,18 @@ imports: - hcl/strconv - json/scanner - json/token -- name: github.com/hashicorp/yamux - version: d1caa6c97c9fc1cc9e83bbe34d0603f9ff0ce8bd -- name: github.com/huin/goupnp - version: 73053506a919bb1af6beb97feb0d53a1eb814eb1 - subpackages: - - dcps/internetgateway1 - - dcps/internetgateway2 - - httpu - - scpd - - soap - - ssdp - name: github.com/inconshreveable/mousetrap version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 -- name: github.com/ipfs/go-ipfs-util - version: f25fcc891281327394bb48000ef0970d11baff2b -- name: github.com/ipfs/go-log - version: 48d644b006ba26f1793bffc46396e981801078e3 -- name: github.com/jackpal/gateway - version: 5795ac81146e01d3fab7bcf21c043c3d6a32b006 -- name: github.com/jackpal/go-nat-pmp - version: 28a68d0c24adce1da43f8df6a57340909ecd7fdd -- name: github.com/jbenet/go-base58 - version: 6237cf65f3a6f7111cd8a42be3590df99a66bc7d -- name: github.com/jbenet/go-msgio - version: 242a3f4ed2d0098bff2f25b1bd32f4254e803b23 - subpackages: - - mpool -- name: github.com/jbenet/go-reuseport - version: dda7388ef3b7f2c95e80fbc8d0206cc5acb8b90b - subpackages: - - singlepoll - - poll -- name: github.com/jbenet/go-sockaddr - version: 2e7ea655c10e4d4d73365f0f073b81b39cb08ee1 - subpackages: - - net -- name: github.com/jbenet/go-stream-muxer - version: 829afa06d6d9f2afb24a02dba841fa9b57390b6c -- name: github.com/jbenet/go-temp-err-catcher - version: aac704a3f4f27190b4ccc05f303a4931fd1241ff -- name: github.com/jbenet/goprocess - version: b497e2f366b8624394fb2e89c10ab607bebdde0b - subpackages: - - context - - periodic -- name: github.com/libp2p/go-addr-util - version: 68fdbb64b5e38b43e760b77446804b457a5ccd40 -- name: github.com/libp2p/go-libp2p - version: 902647966982b076d2168bf790c6cd34b8fc7cf8 - subpackages: - - p2p/host/basic - - p2p/protocol/identify - - p2p/protocol/identify/pb -- name: github.com/libp2p/go-libp2p-conn - version: 95afe5c1049760dc6331bd0720bbdfd0730b22c1 -- name: github.com/libp2p/go-libp2p-crypto - version: b75b5790f5d12e8f283c85f5cfdd40b14693b815 - subpackages: - - pb -- name: github.com/libp2p/go-libp2p-host - version: f8f42d4bd009c695860e920525e5df659924ba00 -- name: github.com/libp2p/go-libp2p-interface-conn - version: 95afdbf0c900237f3b9104f1f7cfd3d56175a241 -- name: github.com/libp2p/go-libp2p-interface-pnet - version: 93babd47ab221881d714afcf6408630c8586781c -- name: github.com/libp2p/go-libp2p-loggables - version: 09990321c4694e2097a507ed650889e47450e440 -- name: github.com/libp2p/go-libp2p-metrics - version: 35c3d1422fced04fe81794e8984d4dc7ce076084 - subpackages: - - conn - - stream -- name: github.com/libp2p/go-libp2p-nat - version: f70b269434176156ea5267793ed77412798b7516 -- name: github.com/libp2p/go-libp2p-net - version: dcad67edbe37dbedcfdb84712ac0fa4c589a5dd3 -- name: github.com/libp2p/go-libp2p-peer - version: c497a0cf30b2c123a8b46641aa1a420f381581a4 -- name: github.com/libp2p/go-libp2p-peerstore - version: 744a149e48eb42e032540507c8545d12cc3b7f6f - subpackages: - - addr -- name: github.com/libp2p/go-libp2p-protocol - version: 40488c03777c16bfcd65da2f675b192863cbc2dc -- name: github.com/libp2p/go-libp2p-secio - version: a796d2f172a82250f6026b55d40d633916c54ebc - subpackages: - - pb -- name: github.com/libp2p/go-libp2p-swarm - version: e61e80c56cc1acae8e79dac01287e29fa9661a69 -- name: github.com/libp2p/go-libp2p-transport - version: 5d3cb5861b59c26052a5fe184e45c381ec17e22d -- name: github.com/libp2p/go-maddr-filter - version: 90aacb5ee155f0d6f3fa8b34d775de842606c0b1 -- name: github.com/libp2p/go-peerstream - version: 0b4003a6faf41b1dfeb766799eed376301e168e7 -- name: github.com/libp2p/go-tcp-transport - version: 9be1dfb53993f98f70abd55dba6175d83961d330 - name: github.com/magiconair/properties version: 51463bfca2576e06c62a8504b5c0f06d61312647 - name: github.com/mitchellh/mapstructure version: d0303fe809921458f417bcf828397a65db30a7e4 -- name: github.com/multiformats/go-multiaddr - version: 33741da7b3f5773a599d4a03c333704fc560ef34 -- name: github.com/multiformats/go-multiaddr-net - version: a7b93d11855f04f56908e1385991eb6a400fcc43 -- name: github.com/multiformats/go-multihash - version: 7aa9f26a231c6f34f4e9fad52bf580fd36627285 -- name: github.com/multiformats/go-multistream - version: b8f1996688ab586031517919b49b1967fca8d5d9 - name: github.com/pelletier/go-buffruneio version: c37440a7cf42ac63b919c752ca73a85067e05992 - name: github.com/pelletier/go-toml version: fe7536c3dee2596cdd23ee9976a17c22bdaae286 -- name: github.com/satori/go.uuid - version: 5bf94b69c6b68ee1b541973bb8e1144db23a194b - name: github.com/Sirupsen/logrus - version: acfabf31db8f45a9174f54a0d48ea4d15627af4d -- name: github.com/spaolacci/murmur3 - version: 0d12bf811670bf6a1a63828dfbd003eded177fce + version: 68cec9f21fbf3ea8d8f98c044bc6ce05f17b267a - name: github.com/spf13/afero version: 9be650865eab0c12963d8753212f4f9c66cdcf12 subpackages: @@ -171,75 +35,22 @@ imports: - name: github.com/spf13/cast version: acbeb36b902d72a7a4c18e8f3241075e7ab763e4 - name: github.com/spf13/cobra - version: 8d4ce3549a0bf0e3569df3aae7423b7743cd05a9 + version: 84f471618b363c4e298af84c2b53aa4f687c5f70 - name: github.com/spf13/jwalterweatherman version: 0efa5202c04663c757d84f90f5219c1250baf94f - name: github.com/spf13/pflag version: e57e3eeb33f795204c1ca35f56c44f83227c6e66 - name: github.com/spf13/viper version: 0967fc9aceab2ce9da34061253ac10fb99bba5b2 -- name: github.com/whyrusleeping/go-logging - version: 0a5b4a6decf577ce8293eca85ec733d7ab92d742 -- name: github.com/whyrusleeping/go-metrics - version: 1ca5caed0cfa95a47fd65a79762286ae626c865c -- name: github.com/whyrusleeping/go-notifier - version: 8d81c69ca17629313f595a9f379cf9b10e6a540b - subpackages: - - Godeps/_workspace/src/github.com/jbenet/goprocess - - Godeps/_workspace/src/github.com/jbenet/goprocess/ratelimit -- name: github.com/whyrusleeping/go-smux-multistream - version: a8f6f9a46f04c6c944000c59a0d06a767e75ae3a -- name: github.com/whyrusleeping/go-smux-spdystream - version: 99ef171dcd9ac9c7324f49c11cc22fc088eb2552 -- name: github.com/whyrusleeping/go-smux-yamux - version: c6704f34736883e7ab16087389d0744b07874821 -- name: github.com/whyrusleeping/mafmt - version: 15300f9d3a2d71db61951a8705d5ea8878764837 -- name: github.com/whyrusleeping/multiaddr-filter - version: e903e4adabd70b78bc9293b6ee4f359afb3f9f59 -- name: github.com/whyrusleeping/ws-transport - version: 9c8230ff4982da41262ad94888229fcf91887b70 -- name: golang.org/x/crypto - version: ab89591268e0c8b748cbe4047b00197516011af5 - subpackages: - - blake2b - - blake2s - - sha3 - - blowfish -- name: golang.org/x/net - version: 84f0e6f92b10139f986b1756e149a7d9de270cdc - subpackages: - - websocket - - html/charset - - html - - html/atom - name: golang.org/x/sys - version: f845067cf72a21fb4929b0e6a35273bd83b56396 + version: b90f89a1e7a9c1f6b918820b3daa7f08488c8594 subpackages: - unix - name: golang.org/x/text - version: 19e51611da83d6be54ddafce4a4af510cb3e9ea4 + version: ab6d1c143672de99b9dfde433b7f6affb278cc74 subpackages: - transform - unicode/norm - - encoding - - encoding/charmap - - encoding/htmlindex - - encoding/internal/identifier - - encoding/internal - - encoding/japanese - - encoding/korean - - encoding/simplifiedchinese - - encoding/traditionalchinese - - encoding/unicode - - language - - internal/utf8internal - - runes - - internal/tag - name: gopkg.in/yaml.v2 version: cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b -- name: leb.io/hashland - version: e13accbe55f7fa03c73c74ace4cca4c425e47260 - subpackages: - - keccakpg testImports: [] diff --git a/glide.yaml b/glide.yaml index d0fde7d..3161239 100644 --- a/glide.yaml +++ b/glide.yaml @@ -1,15 +1,5 @@ package: github.com/ubclaunchpad/cumulus import: -- package: github.com/libp2p/go-libp2p-crypto -- package: github.com/libp2p/go-libp2p-host -- package: github.com/libp2p/go-libp2p-net -- package: github.com/libp2p/go-libp2p-peer -- package: github.com/libp2p/go-libp2p-peerstore -- package: github.com/libp2p/go-libp2p-swarm -- package: github.com/libp2p/go-libp2p - subpackages: - - p2p/host/basic -- package: github.com/multiformats/go-multiaddr - package: github.com/Sirupsen/logrus - package: github.com/google/uuid version: ~0.2.0 diff --git a/peer/peer.go b/peer/peer.go index 3648f9f..b683f6a 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -137,6 +137,8 @@ func ConnectionHandler(c net.Conn) { go p.Dispatch() go p.PushHandler() go p.RequestHandler() + + log.Infof("Connected to %s", p.Connection.RemoteAddr().String()) } // Dispatch listens on this peer's Connection and passes received messages @@ -154,6 +156,7 @@ func (p *Peer) Dispatch() { switch msg.Type() { case message.MessageRequest: p.reqChan <- msg.(*message.Request) + break case message.MessageResponse: res := msg.(*message.Response) resChan := p.resChans.Get(res.ID) @@ -163,8 +166,10 @@ func (p *Peer) Dispatch() { log.Errorf("Dispatcher could not find channel for response %s", res.ID) } p.resChans.Remove(res.ID) + break case message.MessagePush: p.pushChan <- msg.(*message.Push) + break default: // Invalid messgae type. Ignore log.Debug("Dispatcher received message with invalid type") @@ -193,6 +198,7 @@ func (p *Peer) RequestHandler() { case message.ResourceBlock, message.ResourceTransaction: res.Error = message.NewProtocolError(message.NotImplemented, "Block and Transaction requests are not yet implemented on this peer") + break default: res.Error = message.NewProtocolError(message.InvalidResourceType, "Invalid resource type") @@ -220,14 +226,12 @@ func (p *Peer) PushHandler() { switch push.ResourceType { case message.ResourcePeerInfo: for _, addr := range push.Resource.([]string) { - conn.Listen(addr, func(c net.Conn) { - p := New(c, PStore) - p.Store.Add(p) - - go p.Dispatch() - go p.PushHandler() - go p.RequestHandler() - }) + c, err := conn.Dial(addr) + if err != nil { + log.WithError(err).Errorf("PushHandler fialed to dial peer %s", addr) + } else { + ConnectionHandler(c) + } } break case message.ResourceBlock: From b8d39cc47d87c7110ccc27ee01570d33a3703a50 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Wed, 7 Jun 2017 20:25:03 -0700 Subject: [PATCH 29/37] rename message -> msg --- {message => msg}/message.go | 2 +- {message => msg}/message_test.go | 2 +- peer/peer.go | 62 ++++++++++++++++---------------- 3 files changed, 33 insertions(+), 33 deletions(-) rename {message => msg}/message.go (99%) rename {message => msg}/message_test.go (99%) diff --git a/message/message.go b/msg/message.go similarity index 99% rename from message/message.go rename to msg/message.go index 1dce108..73e1cf0 100644 --- a/message/message.go +++ b/msg/message.go @@ -1,4 +1,4 @@ -package message +package msg import ( "encoding/gob" diff --git a/message/message_test.go b/msg/message_test.go similarity index 99% rename from message/message_test.go rename to msg/message_test.go index f7816d8..fe6b991 100644 --- a/message/message_test.go +++ b/msg/message_test.go @@ -1,4 +1,4 @@ -package message +package msg import ( "bytes" diff --git a/peer/peer.go b/peer/peer.go index b683f6a..bdf5f8e 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -8,7 +8,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/google/uuid" "github.com/ubclaunchpad/cumulus/conn" - "github.com/ubclaunchpad/cumulus/message" + "github.com/ubclaunchpad/cumulus/msg" ) const ( @@ -75,12 +75,12 @@ func (ps *PeerStore) Addrs() []string { // ChanStore is a threadsafe container for response channels. type ChanStore struct { - chans map[string]chan *message.Response + chans map[string]chan *msg.Response lock sync.RWMutex } // Add synchronously adds a channel with the given id to the store. -func (cs *ChanStore) Add(id string, channel chan *message.Response) { +func (cs *ChanStore) Add(id string, channel chan *msg.Response) { cs.lock.Lock() defer cs.lock.Lock() cs.chans[id] = channel @@ -94,7 +94,7 @@ func (cs *ChanStore) Remove(id string) { } // Get retrieves the channel with the given ID. -func (cs *ChanStore) Get(id string) chan *message.Response { +func (cs *ChanStore) Get(id string) chan *msg.Response { cs.lock.RLock() defer cs.lock.RUnlock() return cs.chans[id] @@ -106,15 +106,15 @@ type Peer struct { Connection net.Conn Store *PeerStore resChans *ChanStore - reqChan chan *message.Request - pushChan chan *message.Push + reqChan chan *msg.Request + pushChan chan *msg.Push lock sync.RWMutex } // New returns a new Peer func New(c net.Conn, ps *PeerStore) *Peer { cs := &ChanStore{ - chans: make(map[string]chan *message.Response), + chans: make(map[string]chan *msg.Response), lock: sync.RWMutex{}, } return &Peer{ @@ -122,8 +122,8 @@ func New(c net.Conn, ps *PeerStore) *Peer { Connection: c, Store: ps, resChans: cs, - reqChan: make(chan *message.Request), - pushChan: make(chan *message.Push), + reqChan: make(chan *msg.Request), + pushChan: make(chan *msg.Push), } } @@ -147,28 +147,28 @@ func (p *Peer) Dispatch() { p.Connection.SetDeadline(time.Now().Add(Timeout)) for { - msg, err := message.Read(p.Connection) + message, err := msg.Read(p.Connection) if err != nil { log.WithError(err).Error("Dispatcher failed to read message") continue } - switch msg.Type() { - case message.MessageRequest: - p.reqChan <- msg.(*message.Request) + switch message.Type() { + case msg.MessageRequest: + p.reqChan <- message.(*msg.Request) break - case message.MessageResponse: - res := msg.(*message.Response) + case msg.MessageResponse: + res := message.(*msg.Response) resChan := p.resChans.Get(res.ID) if resChan != nil { - resChan <- msg.(*message.Response) + resChan <- message.(*msg.Response) } else { log.Errorf("Dispatcher could not find channel for response %s", res.ID) } p.resChans.Remove(res.ID) break - case message.MessagePush: - p.pushChan <- msg.(*message.Push) + case msg.MessagePush: + p.pushChan <- message.(*msg.Push) break default: // Invalid messgae type. Ignore @@ -180,7 +180,7 @@ func (p *Peer) Dispatch() { // RequestHandler waits on this peer's request channel for incoming requests // from the Dispatcher, responding to each request appropriately. func (p *Peer) RequestHandler() { - var req *message.Request + var req *msg.Request for { select { case req = <-p.reqChan: @@ -189,18 +189,18 @@ func (p *Peer) RequestHandler() { continue } - res := message.Response{ID: req.ID} + res := msg.Response{ID: req.ID} switch req.ResourceType { - case message.ResourcePeerInfo: + case msg.ResourcePeerInfo: res.Resource = p.Store.Addrs() break - case message.ResourceBlock, message.ResourceTransaction: - res.Error = message.NewProtocolError(message.NotImplemented, + case msg.ResourceBlock, msg.ResourceTransaction: + res.Error = msg.NewProtocolError(msg.NotImplemented, "Block and Transaction requests are not yet implemented on this peer") break default: - res.Error = message.NewProtocolError(message.InvalidResourceType, + res.Error = msg.NewProtocolError(msg.InvalidResourceType, "Invalid resource type") } @@ -214,7 +214,7 @@ func (p *Peer) RequestHandler() { // PushHandler waits on this peer's request channel for incoming requests // from the Dispatcher, responding to each request appropriately. func (p *Peer) PushHandler() { - var push *message.Push + var push *msg.Push for { select { case push = <-p.pushChan: @@ -224,7 +224,7 @@ func (p *Peer) PushHandler() { } switch push.ResourceType { - case message.ResourcePeerInfo: + case msg.ResourcePeerInfo: for _, addr := range push.Resource.([]string) { c, err := conn.Dial(addr) if err != nil { @@ -234,8 +234,8 @@ func (p *Peer) PushHandler() { } } break - case message.ResourceBlock: - case message.ResourceTransaction: + case msg.ResourceBlock: + case msg.ResourceTransaction: default: // Invalid resource type. Ignore } @@ -244,7 +244,7 @@ func (p *Peer) PushHandler() { // AwaitResponse waits on a response channel for a response message sent by the // Dispatcher. When a response arrives it is handled appropriately. -func (p *Peer) AwaitResponse(req message.Request, c chan *message.Response) { +func (p *Peer) AwaitResponse(req msg.Request, c chan *msg.Response) { defer p.resChans.Remove(req.ID) select { case res := <-c: @@ -258,8 +258,8 @@ func (p *Peer) AwaitResponse(req message.Request, c chan *message.Response) { // Request sends the given request over this peer's Connection and spawns a // response listener with AwaitResponse. Returns error if request could not be // written. -func (p *Peer) Request(req message.Request) error { - resChan := make(chan *message.Response) +func (p *Peer) Request(req msg.Request) error { + resChan := make(chan *msg.Response) p.resChans.Add(req.ID, resChan) err := req.Write(p.Connection) if err != nil { From b7bda3246928f7e0f1ee7c1bec7e4d2fa22b2048 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Wed, 7 Jun 2017 20:35:14 -0700 Subject: [PATCH 30/37] Simplify Message interface --- msg/message.go | 29 +---------------------------- msg/message_test.go | 12 ------------ peer/peer.go | 10 +++++----- 3 files changed, 6 insertions(+), 45 deletions(-) diff --git a/msg/message.go b/msg/message.go index 73e1cf0..33a2a58 100644 --- a/msg/message.go +++ b/msg/message.go @@ -6,23 +6,12 @@ import ( ) type ( - // Type specifies the type of a message. - Type int // ResourceType specifies the type of a resource in a message. ResourceType int // ErrorCode is a code associated with an error ErrorCode int ) -const ( - // MessageRequest messages ask a peer for a resource. - MessageRequest Type = iota - // MessageResponse messages repond to a request message with an error or a resource. - MessageResponse - // MessagePush messages proactively send a resource to a peer. - MessagePush -) - const ( // ResourcePeerInfo resources contain a list of peers. ResourcePeerInfo ResourceType = iota @@ -39,7 +28,7 @@ const ( // NotImplemented occurs when a message or request is received whos response // requires functionality that does not yet exist. NotImplemented = 501 - // SubnetFull occurs when a stream is opened with a peer who's Subnet is + // SubnetFull occurs when a stream is opened with a peer whose Subnet is // already full. SubnetFull = 503 ) @@ -70,7 +59,6 @@ func (e *ProtocolError) Error() string { // Message is a container for messages, containing a type and either a Request, // Response, or Push in the payload. type Message interface { - Type() Type Write(io.Writer) error } @@ -84,11 +72,6 @@ type Request struct { Params map[string]interface{} } -// Type returns the message type -func (r *Request) Type() Type { - return MessageRequest -} - // Response is a container for a response payload, containing the unique request // ID of the request prompting it, an Error (if one occurred), and the requested // resource (if no error occurred). @@ -98,11 +81,6 @@ type Response struct { Resource interface{} } -// Type returns the message type -func (r *Response) Type() Type { - return MessageResponse -} - // Push is a container for a push payload, containing a resource proactively sent // to us by another peer. type Push struct { @@ -110,11 +88,6 @@ type Push struct { Resource interface{} } -// Type returns the message type -func (p *Push) Type() Type { - return MessagePush -} - // Write encodes and writes the Message into the given Writer. func (r *Request) Write(w io.Writer) error { var m Message = r diff --git a/msg/message_test.go b/msg/message_test.go index fe6b991..d4d5d48 100644 --- a/msg/message_test.go +++ b/msg/message_test.go @@ -28,10 +28,6 @@ func TestRequest(t *testing.T) { t.Fail() } - if out.Type() != MessageRequest { - t.Fail() - } - outReq, ok := out.(*Request) if !ok { t.Fail() @@ -63,10 +59,6 @@ func TestResponse(t *testing.T) { t.Fail() } - if out.Type() != MessageResponse { - t.Fail() - } - outRes, ok := out.(*Response) if !ok { t.Fail() @@ -106,10 +98,6 @@ func TestPush(t *testing.T) { t.Fail() } - if out.Type() != MessagePush { - t.Fail() - } - outPush, ok := out.(*Push) if !ok { t.Fail() diff --git a/peer/peer.go b/peer/peer.go index bdf5f8e..55be29d 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -129,7 +129,7 @@ func New(c net.Conn, ps *PeerStore) *Peer { // ConnectionHandler is called when a new connection is opened with us by a // remote peer. It will create a dispatcher and message handlers to handle -// sending and reveiving messages over the new connection. +// sending and retrieving messages over the new connection. func ConnectionHandler(c net.Conn) { p := New(c, PStore) PStore.Add(p) @@ -153,11 +153,11 @@ func (p *Peer) Dispatch() { continue } - switch message.Type() { - case msg.MessageRequest: + switch message.(type) { + case *msg.Request: p.reqChan <- message.(*msg.Request) break - case msg.MessageResponse: + case *msg.Response: res := message.(*msg.Response) resChan := p.resChans.Get(res.ID) if resChan != nil { @@ -167,7 +167,7 @@ func (p *Peer) Dispatch() { } p.resChans.Remove(res.ID) break - case msg.MessagePush: + case *msg.Push: p.pushChan <- message.(*msg.Push) break default: From c3c5195dfcfebc2673b0f7927fd5cc1c5e1da760 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Wed, 7 Jun 2017 21:18:56 -0700 Subject: [PATCH 31/37] add config and app skeleton --- app/app.go | 20 ++++++++++++++++++++ conf/config.go | 17 +++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 app/app.go create mode 100644 conf/config.go diff --git a/app/app.go b/app/app.go new file mode 100644 index 0000000..d7d404e --- /dev/null +++ b/app/app.go @@ -0,0 +1,20 @@ +package app + +import "github.com/ubclaunchpad/cumulus/conf" + +var ( + config *conf.Config + // TODO peer store once it's merged in +) + +// Run sets up and starts a new Cumulus node with the +// given configuration. +func Run(c conf.Config) { + config = &c + // Create peer store + // Open listening port + // If target specified, try to open connection + + // Ask target for its peers + // Connect to these peers until we have enough peers +} diff --git a/conf/config.go b/conf/config.go new file mode 100644 index 0000000..f49cbc2 --- /dev/null +++ b/conf/config.go @@ -0,0 +1,17 @@ +package conf + +// Config contains all configuration options for a node. +type Config struct { + // The interface to listen on for new connections. + Interface string + // The port to listen on for new connections. + Port uint16 + // The address of the ingress node we should use to connect + // to the network. + Target string + + // Whether or not to enable verbose logging. + Verbose bool + // Whether or not to participate in mining new blocks. + Mine bool +} From 4c4ca96201c43fdef01a345ecaeeff1a6d62db4d Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Sat, 10 Jun 2017 16:44:01 -0700 Subject: [PATCH 32/37] move init to app --- app/app.go | 35 ++++++++++++++++++++++++++++++----- cmd/run.go | 48 ++++++++++-------------------------------------- peer/peer.go | 14 -------------- 3 files changed, 40 insertions(+), 57 deletions(-) diff --git a/app/app.go b/app/app.go index d7d404e..69673bc 100644 --- a/app/app.go +++ b/app/app.go @@ -1,6 +1,14 @@ package app -import "github.com/ubclaunchpad/cumulus/conf" +import ( + "fmt" + + log "github.com/Sirupsen/logrus" + + "github.com/ubclaunchpad/cumulus/conf" + "github.com/ubclaunchpad/cumulus/conn" + "github.com/ubclaunchpad/cumulus/peer" +) var ( config *conf.Config @@ -9,12 +17,29 @@ var ( // Run sets up and starts a new Cumulus node with the // given configuration. -func Run(c conf.Config) { - config = &c +func Run(cfg conf.Config) { + log.Info("Starting Cumulus node") + config = &cfg // Create peer store - // Open listening port - // If target specified, try to open connection + log.Infof("Starting listener on %s:%d", cfg.Interface, cfg.Port) + go func() { + err := conn.Listen(fmt.Sprintf("%s:%d", cfg.Interface, cfg.Port), peer.ConnectionHandler) + if err != nil { + log.WithError(err).Fatalf("Failed to listen on %s:%d", cfg.Interface, cfg.Port) + } + }() + + if len(cfg.Target) > 0 { + log.Infof("Dialing target %s", cfg.Target) + c, err := conn.Dial(cfg.Target) + if err != nil { + log.WithError(err).Fatalf("Failed to connect to target") + } + peer.ConnectionHandler(c) + } + + select {} // Ask target for its peers // Connect to these peers until we have enough peers } diff --git a/cmd/run.go b/cmd/run.go index 428db18..0c29b67 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -1,11 +1,9 @@ package cmd import ( - "fmt" - - log "github.com/Sirupsen/logrus" "github.com/spf13/cobra" - "github.com/ubclaunchpad/cumulus/conn" + "github.com/ubclaunchpad/cumulus/app" + "github.com/ubclaunchpad/cumulus/conf" "github.com/ubclaunchpad/cumulus/peer" ) @@ -17,10 +15,16 @@ var runCmd = &cobra.Command{ If a target is not provided, listen for incoming connections.`, Run: func(cmd *cobra.Command, args []string) { port, _ := cmd.Flags().GetInt("port") - ip, _ := cmd.Flags().GetString("interface") + iface, _ := cmd.Flags().GetString("interface") target, _ := cmd.Flags().GetString("target") verbose, _ := cmd.Flags().GetBool("verbose") - run(port, ip, target, verbose) + config := conf.Config{ + Interface: iface, + Port: uint16(port), + Target: target, + Verbose: verbose, + } + app.Run(config) }, } @@ -40,35 +44,3 @@ func init() { runCmd.Flags().StringP("target", "t", "", "Multiaddress of peer to connect to") runCmd.Flags().BoolP("verbose", "v", false, "Enable verbose logging") } - -func run(port int, ip, target string, verbose bool) { - log.Info("Starting Cumulus Peer") - - if verbose { - log.SetLevel(log.DebugLevel) - } - - // Set up listener. This is run as a goroutine because Listen blocks forever - log.Infof("Starting listener on %s:%d", ip, port) - go func() { - err := conn.Listen(fmt.Sprintf("%s:%d", ip, port), peer.ConnectionHandler) - if err != nil { - log.WithError(err).Fatalf("Failed to listen on %s:%d", ip, port) - } - }() - - // Connect to remote peer if target provided - if target != "" { - log.Infof("Dialing target %s", target) - c, err := conn.Dial(target) - if err != nil { - log.WithError(err).Errorf("Failed to dial target %s", target) - return - } - peer.ConnectionHandler(c) - } - - // Hang forever. All the work from here on is handled in goroutines. We need - // this to hang to keep them alive. - select {} -} diff --git a/peer/peer.go b/peer/peer.go index 55be29d..8d6e4eb 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -7,7 +7,6 @@ import ( log "github.com/Sirupsen/logrus" "github.com/google/uuid" - "github.com/ubclaunchpad/cumulus/conn" "github.com/ubclaunchpad/cumulus/msg" ) @@ -184,7 +183,6 @@ func (p *Peer) RequestHandler() { for { select { case req = <-p.reqChan: - break case <-time.After(Timeout): continue } @@ -194,11 +192,9 @@ func (p *Peer) RequestHandler() { switch req.ResourceType { case msg.ResourcePeerInfo: res.Resource = p.Store.Addrs() - break case msg.ResourceBlock, msg.ResourceTransaction: res.Error = msg.NewProtocolError(msg.NotImplemented, "Block and Transaction requests are not yet implemented on this peer") - break default: res.Error = msg.NewProtocolError(msg.InvalidResourceType, "Invalid resource type") @@ -224,16 +220,6 @@ func (p *Peer) PushHandler() { } switch push.ResourceType { - case msg.ResourcePeerInfo: - for _, addr := range push.Resource.([]string) { - c, err := conn.Dial(addr) - if err != nil { - log.WithError(err).Errorf("PushHandler fialed to dial peer %s", addr) - } else { - ConnectionHandler(c) - } - } - break case msg.ResourceBlock: case msg.ResourceTransaction: default: From 9d32e478027ec7c9b2624838681aec797e30be79 Mon Sep 17 00:00:00 2001 From: Bruno Bachmann Date: Sun, 11 Jun 2017 13:46:45 -0700 Subject: [PATCH 33/37] Add EOF handling logic for reading from connection --- peer/peer.go | 168 ++++++++++++++++++++++----------------------------- 1 file changed, 72 insertions(+), 96 deletions(-) diff --git a/peer/peer.go b/peer/peer.go index 55be29d..f1a096e 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -1,13 +1,13 @@ package peer import ( + "io" "net" "sync" "time" log "github.com/Sirupsen/logrus" "github.com/google/uuid" - "github.com/ubclaunchpad/cumulus/conn" "github.com/ubclaunchpad/cumulus/msg" ) @@ -19,6 +19,9 @@ const ( DefaultIP = "127.0.0.1" // Timeout is the time after which reads from a stream will timeout Timeout = time.Second * 30 + // messageWaitTime is the amount of time the dispatcher should wait before + // attempting to read from the connection again when no data was received + messageWaitTime = time.Second * 5 ) // PStore stores information about every peer we are connected to. All peers we @@ -73,57 +76,31 @@ func (ps *PeerStore) Addrs() []string { return addrs } -// ChanStore is a threadsafe container for response channels. -type ChanStore struct { - chans map[string]chan *msg.Response - lock sync.RWMutex -} - -// Add synchronously adds a channel with the given id to the store. -func (cs *ChanStore) Add(id string, channel chan *msg.Response) { - cs.lock.Lock() - defer cs.lock.Lock() - cs.chans[id] = channel -} - -// Remove synchronously removes the channel with the given ID. -func (cs *ChanStore) Remove(id string) { - cs.lock.Lock() - defer cs.lock.Unlock() - delete(cs.chans, id) -} - -// Get retrieves the channel with the given ID. -func (cs *ChanStore) Get(id string) chan *msg.Response { - cs.lock.RLock() - defer cs.lock.RUnlock() - return cs.chans[id] -} - // Peer represents a remote peer we are connected to. type Peer struct { - ID uuid.UUID - Connection net.Conn - Store *PeerStore - resChans *ChanStore - reqChan chan *msg.Request - pushChan chan *msg.Push - lock sync.RWMutex + ID uuid.UUID + Connection net.Conn + Store *PeerStore + responseHandlers map[string]ResponseHandler + reqChan chan *msg.Request + pushChan chan *msg.Push + killChan chan bool + lock sync.RWMutex } +// ResponseHandler is any function that handles a response to a request. +type ResponseHandler func(*msg.Response) + // New returns a new Peer func New(c net.Conn, ps *PeerStore) *Peer { - cs := &ChanStore{ - chans: make(map[string]chan *msg.Response), - lock: sync.RWMutex{}, - } return &Peer{ - ID: uuid.New(), - Connection: c, - Store: ps, - resChans: cs, - reqChan: make(chan *msg.Request), - pushChan: make(chan *msg.Push), + ID: uuid.New(), + Connection: c, + Store: ps, + responseHandlers: make(map[string]ResponseHandler), + reqChan: make(chan *msg.Request), + pushChan: make(chan *msg.Push), + killChan: make(chan bool), } } @@ -144,32 +121,44 @@ func ConnectionHandler(c net.Conn) { // Dispatch listens on this peer's Connection and passes received messages // to the appropriate message handlers. func (p *Peer) Dispatch() { - p.Connection.SetDeadline(time.Now().Add(Timeout)) + // After 3 consecutive errors we kill this connection and its associated + // handlers using the killChan + errCount := 0 for { message, err := msg.Read(p.Connection) if err != nil { - log.WithError(err).Error("Dispatcher failed to read message") + if err == io.EOF { + // This just means the peer hasn't sent anything + select { + case <-time.After(messageWaitTime): + } + } else { + log.WithError(err).Error("Dispatcher failed to read message") + if errCount == 3 { + p.killChan <- true + p.Connection.Close() + return + } + errCount++ + } continue } + errCount = 0 switch message.(type) { case *msg.Request: p.reqChan <- message.(*msg.Request) - break case *msg.Response: res := message.(*msg.Response) - resChan := p.resChans.Get(res.ID) - if resChan != nil { - resChan <- message.(*msg.Response) - } else { - log.Errorf("Dispatcher could not find channel for response %s", res.ID) + rh := p.getResponseHandler(res.ID) + if rh == nil { + log.Error("Dispatcher could not find response handler for response") } - p.resChans.Remove(res.ID) - break + go rh(res) + p.removeResponseHandler(res.ID) case *msg.Push: p.pushChan <- message.(*msg.Push) - break default: // Invalid messgae type. Ignore log.Debug("Dispatcher received message with invalid type") @@ -184,9 +173,8 @@ func (p *Peer) RequestHandler() { for { select { case req = <-p.reqChan: - break - case <-time.After(Timeout): - continue + case <-p.killChan: + return } res := msg.Response{ID: req.ID} @@ -194,11 +182,9 @@ func (p *Peer) RequestHandler() { switch req.ResourceType { case msg.ResourcePeerInfo: res.Resource = p.Store.Addrs() - break case msg.ResourceBlock, msg.ResourceTransaction: res.Error = msg.NewProtocolError(msg.NotImplemented, "Block and Transaction requests are not yet implemented on this peer") - break default: res.Error = msg.NewProtocolError(msg.InvalidResourceType, "Invalid resource type") @@ -218,22 +204,11 @@ func (p *Peer) PushHandler() { for { select { case push = <-p.pushChan: - break - case <-time.After(Timeout): - continue + case <-p.killChan: + return } switch push.ResourceType { - case msg.ResourcePeerInfo: - for _, addr := range push.Resource.([]string) { - c, err := conn.Dial(addr) - if err != nil { - log.WithError(err).Errorf("PushHandler fialed to dial peer %s", addr) - } else { - ConnectionHandler(c) - } - } - break case msg.ResourceBlock: case msg.ResourceTransaction: default: @@ -242,31 +217,32 @@ func (p *Peer) PushHandler() { } } -// AwaitResponse waits on a response channel for a response message sent by the -// Dispatcher. When a response arrives it is handled appropriately. -func (p *Peer) AwaitResponse(req msg.Request, c chan *msg.Response) { - defer p.resChans.Remove(req.ID) - select { - case res := <-c: - // TODO: do something with the response - log.Debugf("Received response %s", res.ID) - case <-time.After(Timeout): - break - } -} - -// Request sends the given request over this peer's Connection and spawns a -// response listener with AwaitResponse. Returns error if request could not be -// written. -func (p *Peer) Request(req msg.Request) error { - resChan := make(chan *msg.Response) - p.resChans.Add(req.ID, resChan) +// Request sends the given request over this peer's Connection and registers the +// given response hadnler to be called when the response arrives at the dispatcher. +// Returns error if request could not be written. +func (p *Peer) Request(req msg.Request, rh ResponseHandler) error { + p.addResponseHandler(req.ID, rh) err := req.Write(p.Connection) if err != nil { - p.resChans.Remove(req.ID) return err } - - go p.AwaitResponse(req, resChan) return nil } + +func (p *Peer) addResponseHandler(id string, rh ResponseHandler) { + p.lock.Lock() + defer p.lock.Unlock() + p.responseHandlers[id] = rh +} + +func (p *Peer) removeResponseHandler(id string) { + p.lock.Lock() + defer p.lock.Unlock() + delete(p.responseHandlers, id) +} + +func (p *Peer) getResponseHandler(id string) ResponseHandler { + p.lock.Lock() + defer p.lock.Unlock() + return p.responseHandlers[id] +} From 216c99c291055d36ce6debb7186435f2d34d2e64 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Thu, 15 Jun 2017 20:17:26 -0700 Subject: [PATCH 34/37] minor --- app/app.go | 1 + cmd/run.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/app.go b/app/app.go index 69673bc..9670a91 100644 --- a/app/app.go +++ b/app/app.go @@ -42,4 +42,5 @@ func Run(cfg conf.Config) { select {} // Ask target for its peers // Connect to these peers until we have enough peers + // Download the blockchain } diff --git a/cmd/run.go b/cmd/run.go index 0c29b67..d1ff399 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -41,6 +41,6 @@ func init() { // is called directly, e.g.: runCmd.Flags().IntP("port", "p", peer.DefaultPort, "Port to bind to") runCmd.Flags().StringP("interface", "i", peer.DefaultIP, "IP address to listen on") - runCmd.Flags().StringP("target", "t", "", "Multiaddress of peer to connect to") + runCmd.Flags().StringP("target", "t", "", "Address of peer to connect to") runCmd.Flags().BoolP("verbose", "v", false, "Enable verbose logging") } From 8473bd4c8fa0490c245095d5baf131b695aa9d4a Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Thu, 15 Jun 2017 20:25:30 -0700 Subject: [PATCH 35/37] add blockchain to app --- app/app.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/app.go b/app/app.go index 9670a91..cc3a04d 100644 --- a/app/app.go +++ b/app/app.go @@ -5,6 +5,7 @@ import ( log "github.com/Sirupsen/logrus" + "github.com/ubclaunchpad/cumulus/blockchain" "github.com/ubclaunchpad/cumulus/conf" "github.com/ubclaunchpad/cumulus/conn" "github.com/ubclaunchpad/cumulus/peer" @@ -13,6 +14,7 @@ import ( var ( config *conf.Config // TODO peer store once it's merged in + chain *blockchain.BlockChain ) // Run sets up and starts a new Cumulus node with the From 77edb86c98d613def14abb95b4bd289d8bfe6630 Mon Sep 17 00:00:00 2001 From: Bruno Bachmann Date: Sat, 17 Jun 2017 16:32:25 -0700 Subject: [PATCH 36/37] Fix bug in PeerStore, add Peer tests --- peer/peer.go | 6 +- peer/peer_test.go | 283 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 286 insertions(+), 3 deletions(-) create mode 100644 peer/peer_test.go diff --git a/peer/peer.go b/peer/peer.go index bceec9d..48028c9 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -91,9 +91,9 @@ func (ps *PeerStore) Get(addr string) *Peer { func (ps *PeerStore) Addrs() []string { ps.lock.RLock() defer ps.lock.RUnlock() - addrs := make([]string, len(ps.peers), len(ps.peers)) - for _, p := range ps.peers { - addrs = append(addrs, p.Connection.RemoteAddr().String()) + addrs := make([]string, 0) + for addr := range ps.peers { + addrs = append(addrs, addr) } return addrs } diff --git a/peer/peer_test.go b/peer/peer_test.go new file mode 100644 index 0000000..429f270 --- /dev/null +++ b/peer/peer_test.go @@ -0,0 +1,283 @@ +package peer + +import ( + "net" + "testing" + "time" + + "github.com/ubclaunchpad/cumulus/msg" +) + +var ( + addrs1 = []string{ + "50.74.89.63", "80.74.170.128", "252.206.163.100", "106.237.154.62", + "23.37.91.218", "141.205.198.174", "243.116.40.121", "219.202.178.157", + "208.213.114.64", "99.130.197.2", "215.99.177.252", "253.191.123.93", + "118.98.13.210", "217.41.101.17", "94.39.137.0", "8.26.57.127", + "2.121.24.48", "45.166.60.59", "69.14.4.201", "73.11.112.209", + "119.235.160.135", "158.60.36.47", "173.52.51.91", "160.76.117.247", + "99.3.196.77", "26.37.188.143", "252.52.197.42", "189.10.80.173", + "32.15.5.182", "73.178.78.95", "166.109.113.195", "39.137.84.170", + "82.249.125.238", "154.66.246.230", "53.195.164.196", "79.7.11.105", + "98.209.56.64", "74.25.239.123", "211.0.166.153", "54.32.155.245", + "49.37.93.82", "141.54.42.188", "202.15.222.2", "39.251.90.236", + "227.14.254.34", "163.89.37.232", "232.46.225.13", "125.66.77.152", + "134.182.19.76", "4.220.173.35", + } + + addrs2 = []string{ + "7.226.78.25", "90.9.211.160", "201.35.42.214", "71.203.173.18", + "80.33.238.63", "115.122.81.134", "213.234.92.168", "139.190.9.146", + "161.86.170.251", "95.126.157.42", "160.198.2.231", "146.174.248.226", + "206.232.35.67", "99.116.200.57", "95.37.225.234", "227.234.125.133", + "66.40.130.160", "166.32.202.71", "229.203.48.121", "122.41.93.73", + "19.127.139.118", "242.85.174.83", "121.145.63.93", "125.187.226.190", + "227.102.96.138", "133.43.209.108", "245.2.228.113", "43.67.186.61", + "194.70.178.182", "155.98.10.21", "157.150.51.175", "222.1.20.83", + "19.253.228.59", "195.118.45.237", "159.78.10.205", "206.31.54.66", + "31.191.153.165", "130.235.208.32", "130.5.207.98", "5.226.180.24", + } +) + +// fakeConn implements net.Conn +type fakeConn struct { + Addr net.Addr +} + +func (fc fakeConn) Read(b []byte) (n int, err error) { return 0, nil } +func (fc fakeConn) Write(b []byte) (n int, err error) { return 0, nil } +func (fc fakeConn) Close() error { return nil } +func (fc fakeConn) LocalAddr() net.Addr { return fc.Addr } +func (fc fakeConn) RemoteAddr() net.Addr { return fc.Addr } +func (fc fakeConn) SetDeadline(t time.Time) error { return nil } +func (fc fakeConn) SetReadDeadline(t time.Time) error { return nil } +func (fc fakeConn) SetWriteDeadline(t time.Time) error { return nil } + +// fakeAddr implementes net.Addr +type fakeAddr struct { + Addr string +} + +func (fa fakeAddr) Network() string { return fa.Addr } +func (fa fakeAddr) String() string { return fa.Addr } + +func inList(item string, list []string) bool { + for _, listItem := range list { + if item == listItem { + return true + } + } + return false +} + +// This will error if there are concurrent accesses to the PeerStore, or error +// if an atomic operation returns un unexpected result. +func TestConcurrentPeerStore(t *testing.T) { + ps := NewPeerStore() + + resChan1 := make(chan bool) + resChan2 := make(chan bool) + + // Asynchronously add and find peers + go func() { + var fa net.Addr + var fc net.Conn + for _, addr := range addrs2 { + fa = fakeAddr{Addr: addr} + fc = fakeConn{Addr: fa} + ps.Add(New(fc, ps)) + p := ps.Get(addr) + if p.Connection.RemoteAddr().String() != addr { + resChan1 <- false + } + } + resChan1 <- true + }() + + // Asynchronously add and remove peers + go func() { + var fa net.Addr + var fc net.Conn + for _, addr := range addrs1 { + fa = fakeAddr{Addr: addr} + fc = fakeConn{Addr: fa} + ps.Add(New(fc, ps)) + ps.Remove(addr) + } + resChan2 <- true + }() + + returnCount := 0 + for returnCount != 2 { + select { + case res1 := <-resChan1: + if !res1 { + t.FailNow() + } + returnCount++ + case res2 := <-resChan2: + if !res2 { + t.FailNow() + } + returnCount++ + } + } + + if ps.Size() != len(addrs2) { + t.FailNow() + } + + for i := 0; i < len(addrs2); i++ { + p := ps.Get(addrs2[i]) + if p == nil { + t.FailNow() + } + ps.Remove(addrs2[i]) + } + + if ps.Size() != 0 { + t.FailNow() + } +} + +func TestRemoveRandom(t *testing.T) { + var fa fakeAddr + var fc fakeConn + ps := NewPeerStore() + for _, addr := range addrs1 { + fa = fakeAddr{Addr: addr} + fc = fakeConn{Addr: fa} + ps.Add(New(fc, ps)) + } + + for i := ps.Size(); i > 0; i-- { + ps.RemoveRandom() + if ps.Size() != i-1 { + t.FailNow() + } + } +} + +func TestAddrs(t *testing.T) { + var fa fakeAddr + var fc fakeConn + ps := NewPeerStore() + for _, addr := range addrs1 { + fa = fakeAddr{Addr: addr} + fc = fakeConn{Addr: fa} + ps.Add(New(fc, ps)) + } + + addrs := ps.Addrs() + for _, addr := range addrs { + if !inList(addr, addrs1) { + t.FailNow() + } + } +} + +func TestSetRequestHandler(t *testing.T) { + var fa net.Addr + var fc net.Conn + fa = fakeAddr{Addr: "127.0.0.1"} + fc = fakeConn{Addr: fa} + p := New(fc, PStore) + if p.requestHandler != nil { + t.FailNow() + } + + rh := func(req *msg.Request) msg.Response { + return msg.Response{ + ID: "heyyou", + Resource: "i can see you", + } + } + + p.SetRequestHandler(rh) + + if p.requestHandler == nil { + t.FailNow() + } + + p2 := New(fc, PStore) + if p2.requestHandler != nil { + t.FailNow() + } +} + +func TestSetPushHandler(t *testing.T) { + var fa net.Addr + var fc net.Conn + fa = fakeAddr{Addr: "127.0.0.1"} + fc = fakeConn{Addr: fa} + p := New(fc, PStore) + if p.pushHandler != nil { + t.FailNow() + } + + ph := func(req *msg.Push) {} + + p.SetPushHandler(ph) + + if p.pushHandler == nil { + t.FailNow() + } + + p2 := New(fc, PStore) + if p2.pushHandler != nil { + t.FailNow() + } +} + +func TestSetDefaultRequestHandler(t *testing.T) { + var fa net.Addr + var fc net.Conn + fa = fakeAddr{Addr: "127.0.0.1"} + fc = fakeConn{Addr: fa} + p := New(fc, PStore) + if p.requestHandler != nil { + t.FailNow() + } + + rh := func(req *msg.Request) msg.Response { + return msg.Response{ + ID: "heyyou", + Resource: "i can see you", + } + } + + SetDefaultRequestHandler(rh) + + if p.requestHandler != nil { + t.FailNow() + } + + p2 := New(fc, PStore) + if p2.requestHandler == nil { + t.FailNow() + } +} + +func TestSetDefaultPushHandler(t *testing.T) { + var fa net.Addr + var fc net.Conn + fa = fakeAddr{Addr: "127.0.0.1"} + fc = fakeConn{Addr: fa} + p := New(fc, PStore) + if p.pushHandler != nil { + t.FailNow() + } + + ph := func(req *msg.Push) {} + + SetDefaultPushHandler(ph) + + if p.pushHandler != nil { + t.FailNow() + } + + p2 := New(fc, PStore) + if p2.pushHandler == nil { + t.FailNow() + } +} From 9713590ec15068fead282799d209e5204fbccc4d Mon Sep 17 00:00:00 2001 From: Bruno Bachmann Date: Sat, 17 Jun 2017 16:37:55 -0700 Subject: [PATCH 37/37] Merge unmerged code (sorry) --- app/app.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/app.go b/app/app.go index a220bd8..4e364aa 100644 --- a/app/app.go +++ b/app/app.go @@ -64,7 +64,6 @@ func Run(cfg conf.Config) { // peer is running go peer.MaintainConnections() select {} -<<<<<<< HEAD } // RequestHandler is called every time a peer sends us a request message expect @@ -95,9 +94,8 @@ func PushHandler(push *msg.Push) { default: // Invalid resource type. Ignore } -======= + // Ask target for its peers // Connect to these peers until we have enough peers // Download the blockchain ->>>>>>> 63-app-skeleton }