aboutsummaryrefslogtreecommitdiffstats
path: root/peer.go
diff options
context:
space:
mode:
Diffstat (limited to 'peer.go')
-rw-r--r--peer.go892
1 files changed, 892 insertions, 0 deletions
diff --git a/peer.go b/peer.go
new file mode 100644
index 000000000..291ba08e6
--- /dev/null
+++ b/peer.go
@@ -0,0 +1,892 @@
+package eth
+
+import (
+ "bytes"
+ "container/list"
+ "fmt"
+ "math"
+ "math/big"
+ "net"
+ "strconv"
+ "strings"
+ "sync/atomic"
+ "time"
+
+ "github.com/ethereum/go-ethereum/ethchain"
+ "github.com/ethereum/go-ethereum/ethlog"
+ "github.com/ethereum/go-ethereum/ethutil"
+ "github.com/ethereum/go-ethereum/ethwire"
+)
+
+var peerlogger = ethlog.NewLogger("PEER")
+
+const (
+ // The size of the output buffer for writing messages
+ outputBufferSize = 50
+ // Current protocol version
+ ProtocolVersion = 36
+ // Current P2P version
+ P2PVersion = 2
+ // Ethereum network version
+ NetVersion = 0
+ // Interval for ping/pong message
+ pingPongTimer = 2 * time.Second
+)
+
+type DiscReason byte
+
+const (
+ // Values are given explicitly instead of by iota because these values are
+ // defined by the wire protocol spec; it is easier for humans to ensure
+ // correctness when values are explicit.
+ DiscRequested DiscReason = iota
+ DiscReTcpSysErr
+ DiscBadProto
+ DiscBadPeer
+ DiscTooManyPeers
+ DiscConnDup
+ DiscGenesisErr
+ DiscProtoErr
+ DiscQuitting
+)
+
+var discReasonToString = []string{
+ "requested",
+ "TCP sys error",
+ "bad protocol",
+ "useless peer",
+ "too many peers",
+ "already connected",
+ "wrong genesis block",
+ "incompatible network",
+ "quitting",
+}
+
+func (d DiscReason) String() string {
+ if len(discReasonToString) < int(d) {
+ return "Unknown"
+ }
+
+ return discReasonToString[d]
+}
+
+// Peer capabilities
+type Caps byte
+
+const (
+ CapPeerDiscTy Caps = 1 << iota
+ CapTxTy
+ CapChainTy
+
+ CapDefault = CapChainTy | CapTxTy | CapPeerDiscTy
+)
+
+var capsToString = map[Caps]string{
+ CapPeerDiscTy: "Peer discovery",
+ CapTxTy: "Transaction relaying",
+ CapChainTy: "Block chain relaying",
+}
+
+func (c Caps) IsCap(cap Caps) bool {
+ return c&cap > 0
+}
+
+func (c Caps) String() string {
+ var caps []string
+ if c.IsCap(CapPeerDiscTy) {
+ caps = append(caps, capsToString[CapPeerDiscTy])
+ }
+ if c.IsCap(CapChainTy) {
+ caps = append(caps, capsToString[CapChainTy])
+ }
+ if c.IsCap(CapTxTy) {
+ caps = append(caps, capsToString[CapTxTy])
+ }
+
+ return strings.Join(caps, " | ")
+}
+
+type Peer struct {
+ // Ethereum interface
+ ethereum *Ethereum
+ // Net connection
+ conn net.Conn
+ // Output queue which is used to communicate and handle messages
+ outputQueue chan *ethwire.Msg
+ // Quit channel
+ quit chan bool
+ // Determines whether it's an inbound or outbound peer
+ inbound bool
+ // Flag for checking the peer's connectivity state
+ connected int32
+ disconnect int32
+ // Last known message send
+ lastSend time.Time
+ // Indicated whether a verack has been send or not
+ // This flag is used by writeMessage to check if messages are allowed
+ // to be send or not. If no version is known all messages are ignored.
+ versionKnown bool
+ statusKnown bool
+
+ // Last received pong message
+ lastPong int64
+ lastBlockReceived time.Time
+ doneFetchingHashes bool
+
+ host []byte
+ port uint16
+ caps Caps
+ td *big.Int
+ bestHash []byte
+ lastReceivedHash []byte
+ requestedHashes [][]byte
+
+ // This peer's public key
+ pubkey []byte
+
+ // Indicated whether the node is catching up or not
+ catchingUp bool
+ diverted bool
+ blocksRequested int
+
+ version string
+
+ // We use this to give some kind of pingtime to a node, not very accurate, could be improved.
+ pingTime time.Duration
+ pingStartTime time.Time
+
+ lastRequestedBlock *ethchain.Block
+
+ protocolCaps *ethutil.Value
+}
+
+func NewPeer(conn net.Conn, ethereum *Ethereum, inbound bool) *Peer {
+ pubkey := ethereum.KeyManager().PublicKey()[1:]
+
+ return &Peer{
+ outputQueue: make(chan *ethwire.Msg, outputBufferSize),
+ quit: make(chan bool),
+ ethereum: ethereum,
+ conn: conn,
+ inbound: inbound,
+ disconnect: 0,
+ connected: 1,
+ port: 30303,
+ pubkey: pubkey,
+ blocksRequested: 10,
+ caps: ethereum.ServerCaps(),
+ version: ethereum.ClientIdentity().String(),
+ protocolCaps: ethutil.NewValue(nil),
+ td: big.NewInt(0),
+ doneFetchingHashes: true,
+ }
+}
+
+func NewOutboundPeer(addr string, ethereum *Ethereum, caps Caps) *Peer {
+ p := &Peer{
+ outputQueue: make(chan *ethwire.Msg, outputBufferSize),
+ quit: make(chan bool),
+ ethereum: ethereum,
+ inbound: false,
+ connected: 0,
+ disconnect: 0,
+ port: 30303,
+ caps: caps,
+ version: ethereum.ClientIdentity().String(),
+ protocolCaps: ethutil.NewValue(nil),
+ td: big.NewInt(0),
+ doneFetchingHashes: true,
+ }
+
+ // Set up the connection in another goroutine so we don't block the main thread
+ go func() {
+ conn, err := p.Connect(addr)
+ if err != nil {
+ //peerlogger.Debugln("Connection to peer failed. Giving up.", err)
+ p.Stop()
+ return
+ }
+ p.conn = conn
+
+ // Atomically set the connection state
+ atomic.StoreInt32(&p.connected, 1)
+ atomic.StoreInt32(&p.disconnect, 0)
+
+ p.Start()
+ }()
+
+ return p
+}
+
+func (self *Peer) Connect(addr string) (conn net.Conn, err error) {
+ const maxTries = 3
+ for attempts := 0; attempts < maxTries; attempts++ {
+ conn, err = net.DialTimeout("tcp", addr, 10*time.Second)
+ if err != nil {
+ time.Sleep(time.Duration(attempts*20) * time.Second)
+ continue
+ }
+
+ // Success
+ return
+ }
+
+ return
+}
+
+// Getters
+func (p *Peer) PingTime() string {
+ return p.pingTime.String()
+}
+func (p *Peer) Inbound() bool {
+ return p.inbound
+}
+func (p *Peer) LastSend() time.Time {
+ return p.lastSend
+}
+func (p *Peer) LastPong() int64 {
+ return p.lastPong
+}
+func (p *Peer) Host() []byte {
+ return p.host
+}
+func (p *Peer) Port() uint16 {
+ return p.port
+}
+func (p *Peer) Version() string {
+ return p.version
+}
+func (p *Peer) Connected() *int32 {
+ return &p.connected
+}
+
+// Setters
+func (p *Peer) SetVersion(version string) {
+ p.version = version
+}
+
+// Outputs any RLP encoded data to the peer
+func (p *Peer) QueueMessage(msg *ethwire.Msg) {
+ if atomic.LoadInt32(&p.connected) != 1 {
+ return
+ }
+ p.outputQueue <- msg
+}
+
+func (p *Peer) writeMessage(msg *ethwire.Msg) {
+ // Ignore the write if we're not connected
+ if atomic.LoadInt32(&p.connected) != 1 {
+ return
+ }
+
+ if !p.versionKnown {
+ switch msg.Type {
+ case ethwire.MsgHandshakeTy: // Ok
+ default: // Anything but ack is allowed
+ return
+ }
+ } else {
+ /*
+ if !p.statusKnown {
+ switch msg.Type {
+ case ethwire.MsgStatusTy: // Ok
+ default: // Anything but ack is allowed
+ return
+ }
+ }
+ */
+ }
+
+ peerlogger.DebugDetailf("(%v) <= %v\n", p.conn.RemoteAddr(), formatMessage(msg))
+
+ err := ethwire.WriteMessage(p.conn, msg)
+ if err != nil {
+ peerlogger.Debugln(" Can't send message:", err)
+ // Stop the client if there was an error writing to it
+ p.Stop()
+ return
+ }
+}
+
+// Outbound message handler. Outbound messages are handled here
+func (p *Peer) HandleOutbound() {
+ // The ping timer. Makes sure that every 2 minutes a ping is send to the peer
+ pingTimer := time.NewTicker(pingPongTimer)
+ serviceTimer := time.NewTicker(10 * time.Second)
+
+out:
+ for {
+ skip:
+ select {
+ // Main message queue. All outbound messages are processed through here
+ case msg := <-p.outputQueue:
+ if !p.statusKnown {
+ switch msg.Type {
+ case ethwire.MsgTxTy, ethwire.MsgGetBlockHashesTy, ethwire.MsgBlockHashesTy, ethwire.MsgGetBlocksTy, ethwire.MsgBlockTy:
+ break skip
+ }
+ }
+
+ p.writeMessage(msg)
+ p.lastSend = time.Now()
+
+ // Ping timer
+ case <-pingTimer.C:
+ /*
+ timeSince := time.Since(time.Unix(p.lastPong, 0))
+ if !p.pingStartTime.IsZero() && p.lastPong != 0 && timeSince > (pingPongTimer+30*time.Second) {
+ peerlogger.Infof("Peer did not respond to latest pong fast enough, it took %s, disconnecting.\n", timeSince)
+ p.Stop()
+ return
+ }
+ */
+ p.writeMessage(ethwire.NewMessage(ethwire.MsgPingTy, ""))
+ p.pingStartTime = time.Now()
+
+ // Service timer takes care of peer broadcasting, transaction
+ // posting or block posting
+ case <-serviceTimer.C:
+ p.QueueMessage(ethwire.NewMessage(ethwire.MsgGetPeersTy, ""))
+
+ case <-p.quit:
+ // Break out of the for loop if a quit message is posted
+ break out
+ }
+ }
+
+clean:
+ // This loop is for draining the output queue and anybody waiting for us
+ for {
+ select {
+ case <-p.outputQueue:
+ // TODO
+ default:
+ break clean
+ }
+ }
+}
+
+func formatMessage(msg *ethwire.Msg) (ret string) {
+ ret = fmt.Sprintf("%v %v", msg.Type, msg.Data)
+
+ /*
+ XXX Commented out because I need the log level here to determine
+ if i should or shouldn't generate this message
+ */
+ /*
+ switch msg.Type {
+ case ethwire.MsgPeersTy:
+ ret += fmt.Sprintf("(%d entries)", msg.Data.Len())
+ case ethwire.MsgBlockTy:
+ b1, b2 := ethchain.NewBlockFromRlpValue(msg.Data.Get(0)), ethchain.NewBlockFromRlpValue(msg.Data.Get(msg.Data.Len()-1))
+ ret += fmt.Sprintf("(%d entries) %x - %x", msg.Data.Len(), b1.Hash()[0:4], b2.Hash()[0:4])
+ case ethwire.MsgBlockHashesTy:
+ h1, h2 := msg.Data.Get(0).Bytes(), msg.Data.Get(msg.Data.Len()-1).Bytes()
+ ret += fmt.Sprintf("(%d entries) %x - %x", msg.Data.Len(), h1, h2)
+ }
+ */
+
+ return
+}
+
+// Inbound handler. Inbound messages are received here and passed to the appropriate methods
+func (p *Peer) HandleInbound() {
+ for atomic.LoadInt32(&p.disconnect) == 0 {
+
+ // HMM?
+ time.Sleep(50 * time.Millisecond)
+ // Wait for a message from the peer
+ msgs, err := ethwire.ReadMessages(p.conn)
+ if err != nil {
+ peerlogger.Debugln(err)
+ }
+ for _, msg := range msgs {
+ peerlogger.DebugDetailf("(%v) => %v\n", p.conn.RemoteAddr(), formatMessage(msg))
+
+ switch msg.Type {
+ case ethwire.MsgHandshakeTy:
+ // Version message
+ p.handleHandshake(msg)
+
+ //if p.caps.IsCap(CapPeerDiscTy) {
+ p.QueueMessage(ethwire.NewMessage(ethwire.MsgGetPeersTy, ""))
+ //}
+
+ case ethwire.MsgDiscTy:
+ p.Stop()
+ peerlogger.Infoln("Disconnect peer: ", DiscReason(msg.Data.Get(0).Uint()))
+ case ethwire.MsgPingTy:
+ // Respond back with pong
+ p.QueueMessage(ethwire.NewMessage(ethwire.MsgPongTy, ""))
+ case ethwire.MsgPongTy:
+ // If we received a pong back from a peer we set the
+ // last pong so the peer handler knows this peer is still
+ // active.
+ p.lastPong = time.Now().Unix()
+ p.pingTime = time.Since(p.pingStartTime)
+ case ethwire.MsgTxTy:
+ // If the message was a transaction queue the transaction
+ // in the TxPool where it will undergo validation and
+ // processing when a new block is found
+ for i := 0; i < msg.Data.Len(); i++ {
+ tx := ethchain.NewTransactionFromValue(msg.Data.Get(i))
+ p.ethereum.TxPool().QueueTransaction(tx)
+ }
+ case ethwire.MsgGetPeersTy:
+ // Peer asked for list of connected peers
+ //p.pushPeers()
+ case ethwire.MsgPeersTy:
+ // Received a list of peers (probably because MsgGetPeersTy was send)
+ data := msg.Data
+ // Create new list of possible peers for the ethereum to process
+ peers := make([]string, data.Len())
+ // Parse each possible peer
+ for i := 0; i < data.Len(); i++ {
+ value := data.Get(i)
+ peers[i] = unpackAddr(value.Get(0), value.Get(1).Uint())
+ }
+
+ // Connect to the list of peers
+ p.ethereum.ProcessPeerList(peers)
+
+ case ethwire.MsgStatusTy:
+ // Handle peer's status msg
+ p.handleStatus(msg)
+ }
+
+ // TMP
+ if p.statusKnown {
+ switch msg.Type {
+ /*
+ case ethwire.MsgGetTxsTy:
+ // Get the current transactions of the pool
+ txs := p.ethereum.TxPool().CurrentTransactions()
+ // Get the RlpData values from the txs
+ txsInterface := make([]interface{}, len(txs))
+ for i, tx := range txs {
+ txsInterface[i] = tx.RlpData()
+ }
+ // Broadcast it back to the peer
+ p.QueueMessage(ethwire.NewMessage(ethwire.MsgTxTy, txsInterface))
+ */
+
+ case ethwire.MsgGetBlockHashesTy:
+ if msg.Data.Len() < 2 {
+ peerlogger.Debugln("err: argument length invalid ", msg.Data.Len())
+ }
+
+ hash := msg.Data.Get(0).Bytes()
+ amount := msg.Data.Get(1).Uint()
+
+ hashes := p.ethereum.ChainManager().GetChainHashesFromHash(hash, amount)
+
+ p.QueueMessage(ethwire.NewMessage(ethwire.MsgBlockHashesTy, ethutil.ByteSliceToInterface(hashes)))
+
+ case ethwire.MsgGetBlocksTy:
+ // Limit to max 300 blocks
+ max := int(math.Min(float64(msg.Data.Len()), 300.0))
+ var blocks []interface{}
+
+ for i := 0; i < max; i++ {
+ hash := msg.Data.Get(i).Bytes()
+ block := p.ethereum.ChainManager().GetBlock(hash)
+ if block != nil {
+ blocks = append(blocks, block.Value().Raw())
+ }
+ }
+
+ p.QueueMessage(ethwire.NewMessage(ethwire.MsgBlockTy, blocks))
+
+ case ethwire.MsgBlockHashesTy:
+ p.catchingUp = true
+
+ blockPool := p.ethereum.blockPool
+
+ foundCommonHash := false
+
+ it := msg.Data.NewIterator()
+ for it.Next() {
+ hash := it.Value().Bytes()
+ p.lastReceivedHash = hash
+
+ if blockPool.HasCommonHash(hash) {
+ foundCommonHash = true
+
+ break
+ }
+
+ blockPool.AddHash(hash, p)
+ }
+
+ if !foundCommonHash {
+ //if !p.FetchHashes() {
+ // p.doneFetchingHashes = true
+ //}
+ p.FetchHashes()
+ } else {
+ peerlogger.Infof("Found common hash (%x...)\n", p.lastReceivedHash[0:4])
+ p.doneFetchingHashes = true
+ }
+
+ case ethwire.MsgBlockTy:
+ p.catchingUp = true
+
+ blockPool := p.ethereum.blockPool
+
+ it := msg.Data.NewIterator()
+ for it.Next() {
+ block := ethchain.NewBlockFromRlpValue(it.Value())
+ blockPool.Add(block, p)
+
+ p.lastBlockReceived = time.Now()
+ }
+ case ethwire.MsgNewBlockTy:
+ var (
+ blockPool = p.ethereum.blockPool
+ block = ethchain.NewBlockFromRlpValue(msg.Data.Get(0))
+ td = msg.Data.Get(1).BigInt()
+ )
+
+ if td.Cmp(blockPool.td) > 0 {
+ p.ethereum.blockPool.AddNew(block, p)
+ }
+ }
+
+ }
+ }
+ }
+
+ p.Stop()
+}
+
+func (self *Peer) FetchBlocks(hashes [][]byte) {
+ if len(hashes) > 0 {
+ peerlogger.Debugf("Fetching blocks (%d)\n", len(hashes))
+
+ self.QueueMessage(ethwire.NewMessage(ethwire.MsgGetBlocksTy, ethutil.ByteSliceToInterface(hashes)))
+ }
+}
+
+func (self *Peer) FetchHashes() bool {
+ blockPool := self.ethereum.blockPool
+
+ return blockPool.FetchHashes(self)
+}
+
+func (self *Peer) FetchingHashes() bool {
+ return !self.doneFetchingHashes
+}
+
+// General update method
+func (self *Peer) update() {
+ serviceTimer := time.NewTicker(100 * time.Millisecond)
+
+out:
+ for {
+ select {
+ case <-serviceTimer.C:
+ if self.IsCap("eth") {
+ var (
+ sinceBlock = time.Since(self.lastBlockReceived)
+ )
+
+ if sinceBlock > 5*time.Second {
+ self.catchingUp = false
+ }
+ }
+ case <-self.quit:
+ break out
+ }
+ }
+
+ serviceTimer.Stop()
+}
+
+func (p *Peer) Start() {
+ peerHost, peerPort, _ := net.SplitHostPort(p.conn.LocalAddr().String())
+ servHost, servPort, _ := net.SplitHostPort(p.conn.RemoteAddr().String())
+
+ if p.inbound {
+ p.host, p.port = packAddr(peerHost, peerPort)
+ } else {
+ p.host, p.port = packAddr(servHost, servPort)
+ }
+
+ err := p.pushHandshake()
+ if err != nil {
+ peerlogger.Debugln("Peer can't send outbound version ack", err)
+
+ p.Stop()
+
+ return
+ }
+
+ go p.HandleOutbound()
+ // Run the inbound handler in a new goroutine
+ go p.HandleInbound()
+ // Run the general update handler
+ go p.update()
+
+ // Wait a few seconds for startup and then ask for an initial ping
+ time.Sleep(2 * time.Second)
+ p.writeMessage(ethwire.NewMessage(ethwire.MsgPingTy, ""))
+ p.pingStartTime = time.Now()
+
+}
+
+func (p *Peer) Stop() {
+ p.StopWithReason(DiscRequested)
+}
+
+func (p *Peer) StopWithReason(reason DiscReason) {
+ if atomic.AddInt32(&p.disconnect, 1) != 1 {
+ return
+ }
+
+ // Pre-emptively remove the peer; don't wait for reaping. We already know it's dead if we are here
+ p.ethereum.RemovePeer(p)
+
+ close(p.quit)
+ if atomic.LoadInt32(&p.connected) != 0 {
+ p.writeMessage(ethwire.NewMessage(ethwire.MsgDiscTy, reason))
+ p.conn.Close()
+ }
+}
+
+func (p *Peer) peersMessage() *ethwire.Msg {
+ outPeers := make([]interface{}, len(p.ethereum.InOutPeers()))
+ // Serialise each peer
+ for i, peer := range p.ethereum.InOutPeers() {
+ // Don't return localhost as valid peer
+ if !net.ParseIP(peer.conn.RemoteAddr().String()).IsLoopback() {
+ outPeers[i] = peer.RlpData()
+ }
+ }
+
+ // Return the message to the peer with the known list of connected clients
+ return ethwire.NewMessage(ethwire.MsgPeersTy, outPeers)
+}
+
+// Pushes the list of outbound peers to the client when requested
+func (p *Peer) pushPeers() {
+ p.QueueMessage(p.peersMessage())
+}
+
+func (self *Peer) pushStatus() {
+ msg := ethwire.NewMessage(ethwire.MsgStatusTy, []interface{}{
+ uint32(ProtocolVersion),
+ uint32(NetVersion),
+ self.ethereum.ChainManager().TD,
+ self.ethereum.ChainManager().CurrentBlock.Hash(),
+ self.ethereum.ChainManager().Genesis().Hash(),
+ })
+
+ self.QueueMessage(msg)
+}
+
+func (self *Peer) handleStatus(msg *ethwire.Msg) {
+ c := msg.Data
+
+ var (
+ //protoVersion = c.Get(0).Uint()
+ netVersion = c.Get(1).Uint()
+ td = c.Get(2).BigInt()
+ bestHash = c.Get(3).Bytes()
+ genesis = c.Get(4).Bytes()
+ )
+
+ if bytes.Compare(self.ethereum.ChainManager().Genesis().Hash(), genesis) != 0 {
+ ethlogger.Warnf("Invalid genisis hash %x. Disabling [eth]\n", genesis)
+ return
+ }
+
+ if netVersion != NetVersion {
+ ethlogger.Warnf("Invalid network version %d. Disabling [eth]\n", netVersion)
+ return
+ }
+
+ /*
+ if protoVersion != ProtocolVersion {
+ ethlogger.Warnf("Invalid protocol version %d. Disabling [eth]\n", protoVersion)
+ return
+ }
+ */
+
+ // Get the td and last hash
+ self.td = td
+ self.bestHash = bestHash
+ self.lastReceivedHash = bestHash
+
+ self.statusKnown = true
+
+ // Compare the total TD with the blockchain TD. If remote is higher
+ // fetch hashes from highest TD node.
+ self.FetchHashes()
+
+ ethlogger.Infof("Peer is [eth] capable. (TD = %v ~ %x)", self.td, self.bestHash)
+
+}
+
+func (p *Peer) pushHandshake() error {
+ pubkey := p.ethereum.KeyManager().PublicKey()
+ msg := ethwire.NewMessage(ethwire.MsgHandshakeTy, []interface{}{
+ P2PVersion, []byte(p.version), []interface{}{[]interface{}{"eth", ProtocolVersion}}, p.port, pubkey[1:],
+ })
+
+ p.QueueMessage(msg)
+
+ return nil
+}
+
+func (p *Peer) handleHandshake(msg *ethwire.Msg) {
+ c := msg.Data
+
+ var (
+ p2pVersion = c.Get(0).Uint()
+ clientId = c.Get(1).Str()
+ caps = c.Get(2)
+ port = c.Get(3).Uint()
+ pub = c.Get(4).Bytes()
+ )
+
+ // Check correctness of p2p protocol version
+ if p2pVersion != P2PVersion {
+ fmt.Println(p)
+ peerlogger.Debugf("Invalid P2P version. Require protocol %d, received %d\n", P2PVersion, p2pVersion)
+ p.Stop()
+ return
+ }
+
+ // Handle the pub key (validation, uniqueness)
+ if len(pub) == 0 {
+ peerlogger.Warnln("Pubkey required, not supplied in handshake.")
+ p.Stop()
+ return
+ }
+
+ // Self connect detection
+ pubkey := p.ethereum.KeyManager().PublicKey()
+ if bytes.Compare(pubkey[1:], pub) == 0 {
+ p.Stop()
+
+ return
+ }
+
+ // Check for blacklisting
+ for _, pk := range p.ethereum.blacklist {
+ if bytes.Compare(pk, pub) == 0 {
+ peerlogger.Debugf("Blacklisted peer tried to connect (%x...)\n", pubkey[0:4])
+ p.StopWithReason(DiscBadPeer)
+
+ return
+ }
+ }
+
+ usedPub := 0
+ // This peer is already added to the peerlist so we expect to find a double pubkey at least once
+ eachPeer(p.ethereum.Peers(), func(peer *Peer, e *list.Element) {
+ if bytes.Compare(pub, peer.pubkey) == 0 {
+ usedPub++
+ }
+ })
+
+ if usedPub > 0 {
+ peerlogger.Debugf("Pubkey %x found more then once. Already connected to client.", p.pubkey)
+ p.Stop()
+ return
+ }
+ p.pubkey = pub
+
+ // If this is an inbound connection send an ack back
+ if p.inbound {
+ p.port = uint16(port)
+ }
+
+ p.SetVersion(clientId)
+
+ p.versionKnown = true
+
+ p.ethereum.PushPeer(p)
+ p.ethereum.eventMux.Post(PeerListEvent{p.ethereum.Peers()})
+
+ p.protocolCaps = caps
+
+ it := caps.NewIterator()
+ var capsStrs []string
+ for it.Next() {
+ cap := it.Value().Get(0).Str()
+ ver := it.Value().Get(1).Uint()
+ switch cap {
+ case "eth":
+ if ver != ProtocolVersion {
+ ethlogger.Warnf("Invalid protocol version %d. Disabling [eth]\n", ver)
+ continue
+ }
+ p.pushStatus()
+ }
+
+ capsStrs = append(capsStrs, cap)
+ }
+
+ ethlogger.Infof("Added peer (%s) %d / %d (%v)\n", p.conn.RemoteAddr(), p.ethereum.Peers().Len(), p.ethereum.MaxPeers, capsStrs)
+
+ peerlogger.Debugln(p)
+}
+
+func (self *Peer) IsCap(cap string) bool {
+ capsIt := self.protocolCaps.NewIterator()
+ for capsIt.Next() {
+ if capsIt.Value().Str() == cap {
+ return true
+ }
+ }
+
+ return false
+}
+
+func (self *Peer) Caps() *ethutil.Value {
+ return self.protocolCaps
+}
+
+func (p *Peer) String() string {
+ var strBoundType string
+ if p.inbound {
+ strBoundType = "inbound"
+ } else {
+ strBoundType = "outbound"
+ }
+ var strConnectType string
+ if atomic.LoadInt32(&p.disconnect) == 0 {
+ strConnectType = "connected"
+ } else {
+ strConnectType = "disconnected"
+ }
+
+ return fmt.Sprintf("[%s] (%s) %v %s [%s]", strConnectType, strBoundType, p.conn.RemoteAddr(), p.version, p.caps)
+
+}
+
+func (p *Peer) RlpData() []interface{} {
+ return []interface{}{p.host, p.port, p.pubkey}
+}
+
+func packAddr(address, _port string) (host []byte, port uint16) {
+ p, _ := strconv.Atoi(_port)
+ port = uint16(p)
+
+ h := net.ParseIP(address)
+ if ip := h.To4(); ip != nil {
+ host = []byte(ip)
+ } else {
+ host = []byte(h)
+ }
+
+ return
+}
+
+func unpackAddr(value *ethutil.Value, p uint64) string {
+ host, _ := net.IP(value.Bytes()).MarshalText()
+ prt := strconv.Itoa(int(p))
+
+ return net.JoinHostPort(string(host), prt)
+}