plugeth/peer.go

784 lines
22 KiB
Go
Raw Normal View History

2014-01-23 19:14:01 +00:00
package eth
import (
2014-02-13 14:12:16 +00:00
"bytes"
"container/list"
2014-02-10 10:45:08 +00:00
"fmt"
2014-02-14 22:56:09 +00:00
"github.com/ethereum/eth-go/ethchain"
"github.com/ethereum/eth-go/ethutil"
"github.com/ethereum/eth-go/ethwire"
2014-01-23 19:14:01 +00:00
"net"
"strconv"
2014-01-31 19:01:28 +00:00
"strings"
2014-01-23 19:14:01 +00:00
"sync/atomic"
"time"
)
const (
// The size of the output buffer for writing messages
outputBufferSize = 50
2014-03-03 10:34:04 +00:00
// Current protocol version
2014-06-11 19:55:45 +00:00
ProtocolVersion = 20
// Interval for ping/pong message
2014-06-14 22:04:18 +00:00
pingPongTimer = 2 * time.Second
2014-01-23 19:14:01 +00:00
)
2014-02-02 19:00:09 +00:00
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.
2014-02-02 19:00:09 +00:00
DiscReRequested = 0x00
DiscReTcpSysErr = 0x01
DiscBadProto = 0x02
DiscBadPeer = 0x03
DiscTooManyPeers = 0x04
2014-02-10 10:36:49 +00:00
DiscConnDup = 0x05
DiscGenesisErr = 0x06
DiscProtoErr = 0x07
2014-02-02 19:00:09 +00:00
)
var discReasonToString = []string{
"Disconnect requested",
"Disconnect TCP sys error",
2014-02-10 10:36:49 +00:00
"Disconnect bad protocol",
"Disconnect useless peer",
"Disconnect too many peers",
"Disconnect already connected",
"Disconnect wrong genesis block",
"Disconnect incompatible network",
2014-02-02 19:00:09 +00:00
}
func (d DiscReason) String() string {
if len(discReasonToString) < int(d) {
2014-02-02 19:00:09 +00:00
return "Unknown"
}
return discReasonToString[d]
}
// Peer capabilities
2014-01-31 19:01:28 +00:00
type Caps byte
const (
CapPeerDiscTy = 1 << iota
CapTxTy
CapChainTy
2014-02-02 15:15:39 +00:00
2014-02-02 19:00:09 +00:00
CapDefault = CapChainTy | CapTxTy | CapPeerDiscTy
2014-01-31 19:01:28 +00:00
)
var capsToString = map[Caps]string{
2014-02-02 19:00:09 +00:00
CapPeerDiscTy: "Peer discovery",
CapTxTy: "Transaction relaying",
CapChainTy: "Block chain relaying",
2014-01-31 19:01:28 +00:00
}
2014-02-06 12:27:57 +00:00
func (c Caps) IsCap(cap Caps) bool {
return c&cap > 0
}
2014-01-31 19:01:28 +00:00
func (c Caps) String() string {
var caps []string
2014-02-06 12:27:57 +00:00
if c.IsCap(CapPeerDiscTy) {
2014-02-02 19:00:09 +00:00
caps = append(caps, capsToString[CapPeerDiscTy])
2014-01-31 19:01:28 +00:00
}
2014-02-06 12:27:57 +00:00
if c.IsCap(CapChainTy) {
2014-01-31 19:01:28 +00:00
caps = append(caps, capsToString[CapChainTy])
}
2014-02-06 12:27:57 +00:00
if c.IsCap(CapTxTy) {
2014-01-31 19:01:28 +00:00
caps = append(caps, capsToString[CapTxTy])
}
return strings.Join(caps, " | ")
}
2014-01-23 19:14:01 +00:00
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
// Last received pong message
lastPong int64
// Indicates whether a MsgGetPeersTy was requested of the peer
// this to prevent receiving false peers.
requestedPeerList bool
2014-01-30 22:48:52 +00:00
host []byte
2014-01-31 19:01:28 +00:00
port uint16
caps Caps
2014-06-18 08:39:42 +00:00
// This peer's public key
pubkey []byte
2014-02-10 00:09:12 +00:00
// Indicated whether the node is catching up or not
2014-03-21 14:06:23 +00:00
catchingUp bool
diverted bool
2014-03-21 14:06:23 +00:00
blocksRequested int
2014-02-13 14:12:16 +00:00
2014-06-02 13:20:27 +00:00
version string
2014-06-03 08:42:55 +00:00
// 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
2014-01-23 19:14:01 +00:00
}
func NewPeer(conn net.Conn, ethereum *Ethereum, inbound bool) *Peer {
pubkey := ethutil.GetKeyRing().Get(0).PublicKey[1:]
2014-02-13 14:12:16 +00:00
2014-01-23 19:14:01 +00:00
return &Peer{
2014-03-21 14:06:23 +00:00
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(),
2014-06-12 08:07:27 +00:00
version: ethutil.Config.ClientString,
2014-01-23 19:14:01 +00:00
}
}
2014-02-02 15:15:39 +00:00
func NewOutboundPeer(addr string, ethereum *Ethereum, caps Caps) *Peer {
2014-01-23 19:14:01 +00:00
p := &Peer{
outputQueue: make(chan *ethwire.Msg, outputBufferSize),
quit: make(chan bool),
ethereum: ethereum,
inbound: false,
connected: 0,
disconnect: 0,
2014-02-02 15:15:39 +00:00
caps: caps,
2014-06-02 13:20:27 +00:00
version: ethutil.Config.ClientString,
2014-01-23 19:14:01 +00:00
}
// Set up the connection in another goroutine so we don't block the main thread
go func() {
2014-06-18 08:39:42 +00:00
conn, err := net.DialTimeout("tcp", addr, 10*time.Second)
2014-01-27 14:34:50 +00:00
2014-01-23 19:14:01 +00:00
if err != nil {
ethutil.Config.Log.Debugln("Connection to peer failed", err)
2014-01-23 19:14:01 +00:00
p.Stop()
2014-01-27 14:34:50 +00:00
return
2014-01-23 19:14:01 +00:00
}
p.conn = conn
// Atomically set the connection state
atomic.StoreInt32(&p.connected, 1)
atomic.StoreInt32(&p.disconnect, 0)
p.Start()
2014-01-23 19:14:01 +00:00
}()
return p
}
2014-06-02 13:20:27 +00:00
// Getters
2014-06-03 08:42:55 +00:00
func (p *Peer) PingTime() string {
return p.pingTime.String()
}
2014-06-02 13:20:27 +00:00
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
}
2014-01-23 19:14:01 +00:00
// Outputs any RLP encoded data to the peer
func (p *Peer) QueueMessage(msg *ethwire.Msg) {
if atomic.LoadInt32(&p.connected) != 1 {
return
}
2014-01-23 19:14:01 +00:00
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
}
}
2014-06-14 13:44:13 +00:00
ethutil.Config.Log.Println(ethutil.LogLevelSystem, "<=", msg.Type, msg.Data)
2014-01-23 19:14:01 +00:00
err := ethwire.WriteMessage(p.conn, msg)
if err != nil {
ethutil.Config.Log.Debugln("[PEER] Can't send message:", err)
2014-01-23 19:14:01 +00:00
// 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)
2014-02-02 19:06:37 +00:00
serviceTimer := time.NewTicker(5 * time.Minute)
2014-02-11 17:46:28 +00:00
2014-01-23 19:14:01 +00:00
out:
for {
select {
// Main message queue. All outbound messages are processed through here
case msg := <-p.outputQueue:
p.writeMessage(msg)
p.lastSend = time.Now()
// Ping timer
2014-02-02 15:15:39 +00:00
case <-pingTimer.C:
timeSince := time.Since(time.Unix(p.lastPong, 0))
2014-06-14 22:04:18 +00:00
if !p.pingStartTime.IsZero() && p.lastPong != 0 && timeSince > (pingPongTimer+30*time.Second) {
ethutil.Config.Log.Infof("[PEER] Peer did not respond to latest pong fast enough, it took %s, disconnecting.\n", timeSince)
p.Stop()
return
}
2014-01-24 16:48:21 +00:00
p.writeMessage(ethwire.NewMessage(ethwire.MsgPingTy, ""))
2014-06-03 08:42:55 +00:00
p.pingStartTime = time.Now()
2014-01-23 19:14:01 +00:00
2014-02-02 15:15:39 +00:00
// Service timer takes care of peer broadcasting, transaction
// posting or block posting
case <-serviceTimer.C:
2014-02-02 19:00:09 +00:00
if p.caps&CapPeerDiscTy > 0 {
2014-02-02 15:15:39 +00:00
msg := p.peersMessage()
p.ethereum.BroadcastMsg(msg)
}
2014-01-23 19:14:01 +00:00
case <-p.quit:
2014-02-02 15:15:39 +00:00
// Break out of the for loop if a quit message is posted
2014-01-23 19:14:01 +00:00
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
}
}
}
// Inbound handler. Inbound messages are received here and passed to the appropriate methods
func (p *Peer) HandleInbound() {
for atomic.LoadInt32(&p.disconnect) == 0 {
2014-06-03 08:42:55 +00:00
2014-02-10 00:09:12 +00:00
// HMM?
time.Sleep(500 * time.Millisecond)
2014-01-23 19:14:01 +00:00
// Wait for a message from the peer
2014-01-30 22:48:52 +00:00
msgs, err := ethwire.ReadMessages(p.conn)
if err != nil {
ethutil.Config.Log.Debugln(err)
}
for _, msg := range msgs {
2014-06-14 13:44:13 +00:00
ethutil.Config.Log.Println(ethutil.LogLevelSystem, "=>", msg.Type, msg.Data)
2014-01-30 22:48:52 +00:00
switch msg.Type {
case ethwire.MsgHandshakeTy:
// Version message
p.handleHandshake(msg)
2014-01-30 23:56:32 +00:00
2014-02-06 12:27:57 +00:00
if p.caps.IsCap(CapPeerDiscTy) {
p.QueueMessage(ethwire.NewMessage(ethwire.MsgGetPeersTy, ""))
}
2014-01-30 22:48:52 +00:00
case ethwire.MsgDiscTy:
p.Stop()
ethutil.Config.Log.Infoln("Disconnect peer:", DiscReason(msg.Data.Get(0).Uint()))
2014-01-30 22:48:52 +00:00
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()
2014-06-03 08:42:55 +00:00
p.pingTime = time.Now().Sub(p.pingStartTime)
2014-01-30 22:48:52 +00:00
case ethwire.MsgBlockTy:
// Get all blocks and process them
2014-02-13 14:12:16 +00:00
var block, lastBlock *ethchain.Block
var err error
2014-03-21 14:06:23 +00:00
// Make sure we are actually receiving anything
if msg.Data.Len()-1 > 1 && p.diverted {
// We requested blocks and now we need to make sure we have a common ancestor somewhere in these blocks so we can find
// common ground to start syncing from
2014-03-21 14:06:23 +00:00
lastBlock = ethchain.NewBlockFromRlpValue(msg.Data.Get(msg.Data.Len() - 1))
if p.lastRequestedBlock != nil && bytes.Compare(lastBlock.Hash(), p.lastRequestedBlock.Hash()) == 0 {
p.catchingUp = false
continue
}
p.lastRequestedBlock = lastBlock
ethutil.Config.Log.Infof("[PEER] Last block: %x. Checking if we have it locally.\n", lastBlock.Hash())
2014-03-21 14:06:23 +00:00
for i := msg.Data.Len() - 1; i >= 0; i-- {
block = ethchain.NewBlockFromRlpValue(msg.Data.Get(i))
// Do we have this block on our chain? If so we can continue
2014-04-30 15:43:48 +00:00
if !p.ethereum.StateManager().BlockChain().HasBlock(block.Hash()) {
2014-03-21 14:06:23 +00:00
// We don't have this block, but we do have a block with the same prevHash, diversion time!
if p.ethereum.StateManager().BlockChain().HasBlockWithPrevHash(block.PrevHash) {
p.diverted = false
if !p.ethereum.StateManager().BlockChain().FindCanonicalChainFromMsg(msg, block.PrevHash) {
p.SyncWithPeerToLastKnown()
}
break
2014-03-21 14:06:23 +00:00
}
}
}
if !p.ethereum.StateManager().BlockChain().HasBlock(lastBlock.Hash()) {
// If we can't find a common ancenstor we need to request more blocks.
// FIXME: At one point this won't scale anymore since we are not asking for an offset
// we just keep increasing the amount of blocks.
p.blocksRequested = p.blocksRequested * 2
ethutil.Config.Log.Infof("[PEER] No common ancestor found, requesting %d more blocks.\n", p.blocksRequested)
p.catchingUp = false
p.FindCommonParentBlock()
break
}
2014-03-21 14:06:23 +00:00
}
2014-02-13 14:12:16 +00:00
for i := msg.Data.Len() - 1; i >= 0; i-- {
block = ethchain.NewBlockFromRlpValue(msg.Data.Get(i))
2014-03-17 09:33:03 +00:00
//p.ethereum.StateManager().PrepareDefault(block)
2014-05-21 09:42:20 +00:00
//state := p.ethereum.StateManager().CurrentState()
err = p.ethereum.StateManager().Process(block, false)
2014-01-30 22:48:52 +00:00
if err != nil {
2014-02-16 19:33:23 +00:00
if ethutil.Config.Debug {
ethutil.Config.Log.Infof("[PEER] Block %x failed\n", block.Hash())
ethutil.Config.Log.Infof("[PEER] %v\n", err)
ethutil.Config.Log.Debugln(block)
2014-02-16 19:33:23 +00:00
}
2014-02-13 14:12:16 +00:00
break
} else {
lastBlock = block
2014-01-30 22:48:52 +00:00
}
2014-01-25 16:13:33 +00:00
}
2014-02-10 10:20:42 +00:00
if msg.Data.Len() == 0 {
// Set catching up to false if
// the peer has nothing left to give
p.catchingUp = false
}
2014-02-13 14:12:16 +00:00
if err != nil {
// If the parent is unknown try to catch up with this peer
if ethchain.IsParentErr(err) {
2014-06-13 14:06:27 +00:00
ethutil.Config.Log.Infoln("Attempting to catch. Parent known")
2014-02-13 14:12:16 +00:00
p.catchingUp = false
2014-03-21 14:06:23 +00:00
p.CatchupWithPeer(p.ethereum.BlockChain().CurrentBlock.Hash())
2014-02-14 22:56:09 +00:00
} else if ethchain.IsValidationErr(err) {
fmt.Println("Err:", err)
2014-05-14 18:35:23 +00:00
p.catchingUp = false
2014-02-13 14:12:16 +00:00
}
} else {
// If we're catching up, try to catch up further.
if p.catchingUp && msg.Data.Len() > 1 {
if lastBlock != nil {
2014-02-13 14:12:16 +00:00
blockInfo := lastBlock.BlockInfo()
2014-06-19 23:10:39 +00:00
ethutil.Config.Log.Printf(ethutil.LogLevelSystem, "Synced chain to #%d %x %x\n", blockInfo.Number, lastBlock.Hash(), blockInfo.Hash)
2014-02-13 14:12:16 +00:00
}
2014-05-14 18:35:23 +00:00
2014-02-13 14:12:16 +00:00
p.catchingUp = false
2014-05-14 18:35:23 +00:00
hash := p.ethereum.BlockChain().CurrentBlock.Hash()
p.CatchupWithPeer(hash)
2014-02-10 10:36:49 +00:00
}
2014-02-10 10:20:42 +00:00
}
2014-01-30 22:48:52 +00:00
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
2014-02-13 14:12:16 +00:00
for i := 0; i < msg.Data.Len(); i++ {
tx := ethchain.NewTransactionFromValue(msg.Data.Get(i))
p.ethereum.TxPool().QueueTransaction(tx)
2014-01-23 19:14:01 +00:00
}
2014-01-30 22:48:52 +00:00
case ethwire.MsgGetPeersTy:
// Flag this peer as a 'requested of new peers' this to
// prevent malicious peers being forced.
p.requestedPeerList = true
// Peer asked for list of connected peers
p.pushPeers()
case ethwire.MsgPeersTy:
// Received a list of peers (probably because MsgGetPeersTy was send)
// Only act on message if we actually requested for a peers list
2014-05-12 14:09:23 +00:00
if p.requestedPeerList {
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())
}
2014-01-30 22:48:52 +00:00
2014-05-12 14:09:23 +00:00
// Connect to the list of peers
p.ethereum.ProcessPeerList(peers)
// Mark unrequested again
p.requestedPeerList = false
2014-01-23 19:14:01 +00:00
2014-05-12 14:09:23 +00:00
}
2014-01-30 22:48:52 +00:00
case ethwire.MsgGetChainTy:
var parent *ethchain.Block
// Length minus one since the very last element in the array is a count
2014-02-13 14:12:16 +00:00
l := msg.Data.Len() - 1
2014-01-30 22:48:52 +00:00
// Ignore empty get chains
2014-02-06 12:27:57 +00:00
if l == 0 {
2014-01-28 14:35:44 +00:00
break
2014-01-27 14:34:50 +00:00
}
2014-01-30 22:48:52 +00:00
// Amount of parents in the canonical chain
2014-02-08 20:02:42 +00:00
//amountOfBlocks := msg.Data.Get(l).AsUint()
amountOfBlocks := uint64(100)
2014-03-21 14:06:23 +00:00
2014-01-30 22:48:52 +00:00
// Check each SHA block hash from the message and determine whether
// the SHA is in the database
for i := 0; i < l; i++ {
2014-03-21 14:06:23 +00:00
if data := msg.Data.Get(i).Bytes(); p.ethereum.StateManager().BlockChain().HasBlock(data) {
parent = p.ethereum.BlockChain().GetBlock(data)
2014-01-30 22:48:52 +00:00
break
}
}
// If a parent is found send back a reply
if parent != nil {
2014-06-19 23:10:39 +00:00
ethutil.Config.Log.Printf(ethutil.LogLevelSystem, "[PEER] Found canonical block, returning chain from: %x ", parent.Hash())
chain := p.ethereum.BlockChain().GetChainFromHash(parent.Hash(), amountOfBlocks)
2014-04-29 10:36:27 +00:00
if len(chain) > 0 {
2014-05-29 21:54:48 +00:00
//ethutil.Config.Log.Debugf("[PEER] Returning %d blocks: %x ", len(chain), parent.Hash())
2014-04-29 10:36:27 +00:00
p.QueueMessage(ethwire.NewMessage(ethwire.MsgBlockTy, chain))
2014-05-14 18:35:23 +00:00
} else {
2014-05-14 18:36:21 +00:00
p.QueueMessage(ethwire.NewMessage(ethwire.MsgBlockTy, []interface{}{}))
2014-04-29 10:36:27 +00:00
}
2014-05-14 18:35:23 +00:00
2014-01-30 22:48:52 +00:00
} else {
//ethutil.Config.Log.Debugf("[PEER] Could not find a similar block")
2014-01-30 22:48:52 +00:00
// If no blocks are found we send back a reply with msg not in chain
// and the last hash from get chain
if l > 0 {
lastHash := msg.Data.Get(l - 1)
//log.Printf("Sending not in chain with hash %x\n", lastHash.AsRaw())
p.QueueMessage(ethwire.NewMessage(ethwire.MsgNotInChainTy, []interface{}{lastHash.Raw()}))
}
2014-01-30 22:48:52 +00:00
}
case ethwire.MsgNotInChainTy:
2014-06-19 23:10:39 +00:00
ethutil.Config.Log.Printf(ethutil.LogLevelSystem, "Not in chain: %x\n", msg.Data.Get(0).Bytes())
if p.diverted == true {
// If were already looking for a common parent and we get here again we need to go deeper
p.blocksRequested = p.blocksRequested * 2
}
p.diverted = true
p.catchingUp = false
p.FindCommonParentBlock()
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))
2014-01-25 16:13:33 +00:00
2014-01-30 22:48:52 +00:00
// Unofficial but fun nonetheless
case ethwire.MsgTalkTy:
ethutil.Config.Log.Infoln("%v says: %s\n", p.conn.RemoteAddr(), msg.Data.Str())
2014-01-30 22:48:52 +00:00
}
2014-01-23 19:14:01 +00:00
}
}
p.Stop()
}
func (p *Peer) Start() {
2014-01-31 19:01:28 +00:00
peerHost, peerPort, _ := net.SplitHostPort(p.conn.LocalAddr().String())
servHost, servPort, _ := net.SplitHostPort(p.conn.RemoteAddr().String())
2014-01-30 23:56:32 +00:00
2014-01-31 19:01:28 +00:00
if p.inbound {
p.host, p.port = packAddr(peerHost, peerPort)
} else {
p.host, p.port = packAddr(servHost, servPort)
2014-01-30 23:56:32 +00:00
}
err := p.pushHandshake()
if err != nil {
ethutil.Config.Log.Debugln("Peer can't send outbound version ack", err)
2014-01-30 23:56:32 +00:00
p.Stop()
return
2014-01-23 19:14:01 +00:00
}
go p.HandleOutbound()
// Run the inbound handler in a new goroutine
go p.HandleInbound()
2014-01-30 22:48:52 +00:00
2014-06-03 08:42:55 +00:00
// 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()
2014-01-23 19:14:01 +00:00
}
func (p *Peer) Stop() {
if atomic.AddInt32(&p.disconnect, 1) != 1 {
return
}
close(p.quit)
if atomic.LoadInt32(&p.connected) != 0 {
2014-01-25 16:13:33 +00:00
p.writeMessage(ethwire.NewMessage(ethwire.MsgDiscTy, ""))
2014-01-23 19:14:01 +00:00
p.conn.Close()
}
// Pre-emptively remove the peer; don't wait for reaping. We already know it's dead if we are here
p.ethereum.RemovePeer(p)
2014-01-23 19:14:01 +00:00
}
func (p *Peer) pushHandshake() error {
keyRing := ethutil.GetKeyRing().Get(0)
if keyRing != nil {
pubkey := keyRing.PublicKey
2014-02-10 12:59:05 +00:00
msg := ethwire.NewMessage(ethwire.MsgHandshakeTy, []interface{}{
2014-06-12 08:07:27 +00:00
uint32(ProtocolVersion), uint32(0), []byte(p.version), byte(p.caps), p.port, pubkey[1:],
})
2014-01-23 19:14:01 +00:00
p.QueueMessage(msg)
}
2014-01-23 19:14:01 +00:00
return nil
}
2014-02-02 15:15:39 +00:00
func (p *Peer) peersMessage() *ethwire.Msg {
outPeers := make([]interface{}, len(p.ethereum.InOutPeers()))
2014-01-23 19:14:01 +00:00
// 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()
}
2014-01-23 19:14:01 +00:00
}
2014-02-02 15:15:39 +00:00
// Return the message to the peer with the known list of connected clients
return ethwire.NewMessage(ethwire.MsgPeersTy, outPeers)
}
2014-01-23 19:14:01 +00:00
2014-02-02 15:15:39 +00:00
// Pushes the list of outbound peers to the client when requested
func (p *Peer) pushPeers() {
p.QueueMessage(p.peersMessage())
2014-01-23 19:14:01 +00:00
}
func (p *Peer) handleHandshake(msg *ethwire.Msg) {
c := msg.Data
// Set pubkey
p.pubkey = c.Get(5).Bytes()
if p.pubkey == nil {
//ethutil.Config.Log.Debugln("Pubkey required, not supplied in handshake.")
p.Stop()
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(p.pubkey, peer.pubkey) == 0 {
usedPub++
}
})
if usedPub > 0 {
//ethutil.Config.Log.Debugf("Pubkey %x found more then once. Already connected to client.", p.pubkey)
p.Stop()
return
}
2014-03-03 10:34:04 +00:00
if c.Get(0).Uint() != ProtocolVersion {
2014-06-11 19:55:45 +00:00
ethutil.Config.Log.Debugf("Invalid peer version. Require protocol: %d. Received: %d\n", ProtocolVersion, c.Get(0).Uint())
p.Stop()
return
}
2014-02-10 00:09:12 +00:00
// [PROTOCOL_VERSION, NETWORK_ID, CLIENT_ID, CAPS, PORT, PUBKEY]
2014-01-23 19:14:01 +00:00
p.versionKnown = true
// If this is an inbound connection send an ack back
if p.inbound {
2014-02-13 14:12:16 +00:00
p.port = uint16(c.Get(4).Uint())
2014-01-23 19:14:01 +00:00
2014-02-10 00:09:12 +00:00
// Self connect detection
keyPair := ethutil.GetKeyRing().Get(0)
if bytes.Compare(keyPair.PublicKey, p.pubkey) == 0 {
2014-02-13 14:12:16 +00:00
p.Stop()
2014-02-09 22:58:59 +00:00
2014-02-13 14:12:16 +00:00
return
}
}
2014-02-09 22:58:59 +00:00
2014-02-13 14:12:16 +00:00
// Set the peer's caps
p.caps = Caps(c.Get(3).Byte())
2014-06-02 13:20:27 +00:00
2014-02-13 14:12:16 +00:00
// Get a reference to the peers version
2014-06-02 13:20:27 +00:00
versionString := c.Get(2).Str()
if len(versionString) > 0 {
p.SetVersion(c.Get(2).Str())
}
2014-01-30 22:48:52 +00:00
p.ethereum.PushPeer(p)
p.ethereum.reactor.Post("peerList", p.ethereum.Peers())
ethutil.Config.Log.Infof("[SERV] Added peer (%s) %d / %d\n", p.conn.RemoteAddr(), p.ethereum.Peers().Len(), p.ethereum.MaxPeers)
// Catch up with the connected peer
if !p.ethereum.IsUpToDate() {
ethutil.Config.Log.Debugln("Already syncing up with a peer; sleeping")
time.Sleep(10 * time.Second)
}
p.SyncWithPeerToLastKnown()
ethutil.Config.Log.Debugln("[PEER]", p)
2014-02-13 14:12:16 +00:00
}
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"
2014-01-23 19:14:01 +00:00
}
2014-01-30 22:48:52 +00:00
2014-06-02 13:20:27 +00:00
return fmt.Sprintf("[%s] (%s) %v %s [%s]", strConnectType, strBoundType, p.conn.RemoteAddr(), p.version, p.caps)
2014-01-30 23:56:32 +00:00
2014-01-31 19:01:28 +00:00
}
func (p *Peer) SyncWithPeerToLastKnown() {
p.catchingUp = false
p.CatchupWithPeer(p.ethereum.BlockChain().CurrentBlock.Hash())
}
2014-03-21 14:06:23 +00:00
func (p *Peer) FindCommonParentBlock() {
if p.catchingUp {
return
}
2014-03-21 14:06:23 +00:00
p.catchingUp = true
if p.blocksRequested == 0 {
p.blocksRequested = 20
}
blocks := p.ethereum.BlockChain().GetChain(p.ethereum.BlockChain().CurrentBlock.Hash(), p.blocksRequested)
2014-03-21 14:06:23 +00:00
var hashes []interface{}
for _, block := range blocks {
hashes = append(hashes, block.Hash())
2014-03-21 14:06:23 +00:00
}
2014-01-30 23:56:32 +00:00
msgInfo := append(hashes, uint64(len(hashes)))
2014-06-19 23:10:39 +00:00
ethutil.Config.Log.Printf(ethutil.LogLevelSystem, "Asking for block from %x (%d total) from %s\n", p.ethereum.BlockChain().CurrentBlock.Hash(), len(hashes), p.conn.RemoteAddr().String())
msg := ethwire.NewMessage(ethwire.MsgGetChainTy, msgInfo)
p.QueueMessage(msg)
}
2014-03-21 14:06:23 +00:00
func (p *Peer) CatchupWithPeer(blockHash []byte) {
2014-02-10 00:09:12 +00:00
if !p.catchingUp {
// Make sure nobody else is catching up when you want to do this
2014-02-10 00:09:12 +00:00
p.catchingUp = true
2014-03-21 14:06:23 +00:00
msg := ethwire.NewMessage(ethwire.MsgGetChainTy, []interface{}{blockHash, uint64(50)})
2014-02-10 00:09:12 +00:00
p.QueueMessage(msg)
2014-06-19 23:10:39 +00:00
ethutil.Config.Log.Printf(ethutil.LogLevelSystem, "Requesting blockchain %x... from peer %s\n", p.ethereum.BlockChain().CurrentBlock.Hash()[:4], p.conn.RemoteAddr())
2014-05-21 10:39:15 +00:00
msg = ethwire.NewMessage(ethwire.MsgGetTxsTy, []interface{}{})
p.QueueMessage(msg)
2014-02-10 00:09:12 +00:00
}
}
2014-01-31 19:01:28 +00:00
func (p *Peer) RlpData() []interface{} {
return []interface{}{p.host, p.port, p.pubkey}
2014-01-23 19:14:01 +00:00
}
2014-05-01 20:13:59 +00:00
func packAddr(address, port string) ([]byte, uint16) {
2014-05-01 20:13:59 +00:00
addr := strings.Split(address, ".")
a, _ := strconv.Atoi(addr[0])
b, _ := strconv.Atoi(addr[1])
c, _ := strconv.Atoi(addr[2])
d, _ := strconv.Atoi(addr[3])
host := []byte{byte(a), byte(b), byte(c), byte(d)}
2014-05-01 20:13:59 +00:00
prt, _ := strconv.Atoi(port)
return host, uint16(prt)
}
func unpackAddr(value *ethutil.Value, p uint64) string {
byts := value.Bytes()
a := strconv.Itoa(int(byts[0]))
b := strconv.Itoa(int(byts[1]))
c := strconv.Itoa(int(byts[2]))
d := strconv.Itoa(int(byts[3]))
2014-05-01 20:13:59 +00:00
host := strings.Join([]string{a, b, c, d}, ".")
port := strconv.Itoa(int(p))
return net.JoinHostPort(host, port)
}