From e46ab3bdcde7236c8fe54d6c83655e50bd19fe31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 27 Oct 2015 15:10:30 +0200 Subject: [PATCH] eth, p2p, rpc/api: polish protocol info gathering --- eth/backend.go | 60 +++---------------------------------------- eth/handler.go | 42 +++++++++++++++++++++++++++---- eth/helper_test.go | 2 +- eth/peer.go | 49 +++++++++++++++++++++++------------- eth/protocol.go | 3 +++ eth/sync_test.go | 4 +-- p2p/peer.go | 46 +++++++++++++++++++++++++++++++++ p2p/protocol.go | 15 ++++++++++- p2p/server.go | 63 ++++++++++++++++++++++++++++++++++++++++++++++ rpc/api/admin.go | 9 ++++--- rpc/api/utils.go | 2 +- 11 files changed, 209 insertions(+), 86 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index ee857e146..1f3a52d5a 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -472,62 +472,10 @@ func New(config *Config) (*Ethereum, error) { return eth, nil } -type NodeInfo struct { - Name string - NodeUrl string - NodeID string - IP string - DiscPort int // UDP listening port for discovery protocol - TCPPort int // TCP listening port for RLPx - Td string - ListenAddr string -} - -func (s *Ethereum) NodeInfo() *NodeInfo { - node := s.net.Self() - - return &NodeInfo{ - Name: s.Name(), - NodeUrl: node.String(), - NodeID: node.ID.String(), - IP: node.IP.String(), - DiscPort: int(node.UDP), - TCPPort: int(node.TCP), - ListenAddr: s.net.ListenAddr, - Td: s.BlockChain().GetTd(s.BlockChain().CurrentBlock().Hash()).String(), - } -} - -type PeerInfo struct { - ID string - Name string - Caps string - RemoteAddress string - LocalAddress string -} - -func newPeerInfo(peer *p2p.Peer) *PeerInfo { - var caps []string - for _, cap := range peer.Caps() { - caps = append(caps, cap.String()) - } - return &PeerInfo{ - ID: peer.ID().String(), - Name: peer.Name(), - Caps: strings.Join(caps, ", "), - RemoteAddress: peer.RemoteAddr().String(), - LocalAddress: peer.LocalAddr().String(), - } -} - -// PeersInfo returns an array of PeerInfo objects describing connected peers -func (s *Ethereum) PeersInfo() (peersinfo []*PeerInfo) { - for _, peer := range s.net.Peers() { - if peer != nil { - peersinfo = append(peersinfo, newPeerInfo(peer)) - } - } - return +// Network retrieves the underlying P2P network server. This should eventually +// be moved out into a protocol independent package, but for now use an accessor. +func (s *Ethereum) Network() *p2p.Server { + return s.net } func (s *Ethereum) ResetWithGenesisBlock(gb *types.Block) { diff --git a/eth/handler.go b/eth/handler.go index 7dc7de80e..d8c5b4b64 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -34,6 +34,7 @@ import ( "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/pow" "github.com/ethereum/go-ethereum/rlp" ) @@ -55,6 +56,8 @@ type hashFetcherFn func(common.Hash) error type blockFetcherFn func([]common.Hash) error type ProtocolManager struct { + networkId int + fastSync bool txpool txPool blockchain *core.BlockChain @@ -91,6 +94,7 @@ func NewProtocolManager(fastSync bool, networkId int, mux *event.TypeMux, txpool } // Create the protocol manager with the base fields manager := &ProtocolManager{ + networkId: networkId, fastSync: fastSync, eventMux: mux, txpool: txpool, @@ -111,14 +115,23 @@ func NewProtocolManager(fastSync bool, networkId int, mux *event.TypeMux, txpool // Compatible; initialise the sub-protocol version := version // Closure for the run manager.SubProtocols = append(manager.SubProtocols, p2p.Protocol{ - Name: "eth", + Name: ProtocolName, Version: version, Length: ProtocolLengths[i], Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { - peer := manager.newPeer(int(version), networkId, p, rw) + peer := manager.newPeer(int(version), p, rw) manager.newPeerCh <- peer return manager.handle(peer) }, + NodeInfo: func() interface{} { + return manager.NodeInfo() + }, + PeerInfo: func(id discover.NodeID) interface{} { + if p := manager.peers.Peer(fmt.Sprintf("%x", id[:8])); p != nil { + return p.Info() + } + return nil + }, }) } if len(manager.SubProtocols) == 0 { @@ -188,8 +201,8 @@ func (pm *ProtocolManager) Stop() { glog.V(logger.Info).Infoln("Ethereum protocol handler stopped") } -func (pm *ProtocolManager) newPeer(pv, nv int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer { - return newPeer(pv, nv, p, newMeteredMsgWriter(rw)) +func (pm *ProtocolManager) newPeer(pv int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer { + return newPeer(pv, p, newMeteredMsgWriter(rw)) } // handle is the callback invoked to manage the life cycle of an eth peer. When @@ -199,7 +212,7 @@ func (pm *ProtocolManager) handle(p *peer) error { // Execute the Ethereum handshake td, head, genesis := pm.blockchain.Status() - if err := p.Handshake(td, head, genesis); err != nil { + if err := p.Handshake(pm.networkId, td, head, genesis); err != nil { glog.V(logger.Debug).Infof("%v: handshake failed: %v", p, err) return err } @@ -730,3 +743,22 @@ func (self *ProtocolManager) txBroadcastLoop() { self.BroadcastTx(event.Tx.Hash(), event.Tx) } } + +// EthNodeInfo represents a short summary of the Ethereum sub-protocol metadata known +// about the host peer. +type EthNodeInfo struct { + Network int `json:"network"` // Ethereum network ID (0=Olympic, 1=Frontier, 2=Morden) + Difficulty *big.Int `json:"difficulty"` // Total difficulty of the host's blockchain + Genesis string `json:"genesis"` // SHA3 hash of the host's genesis block + Head string `json:"head"` // SHA3 hash of the host's best owned block +} + +// NodeInfo retrieves some protocol metadata about the running host node. +func (self *ProtocolManager) NodeInfo() *EthNodeInfo { + return &EthNodeInfo{ + Network: self.networkId, + Difficulty: self.blockchain.GetTd(self.blockchain.CurrentBlock().Hash()), + Genesis: fmt.Sprintf("%x", self.blockchain.Genesis().Hash()), + Head: fmt.Sprintf("%x", self.blockchain.CurrentBlock().Hash()), + } +} diff --git a/eth/helper_test.go b/eth/helper_test.go index 16907be8b..65fccf7b4 100644 --- a/eth/helper_test.go +++ b/eth/helper_test.go @@ -117,7 +117,7 @@ func newTestPeer(name string, version int, pm *ProtocolManager, shake bool) (*te var id discover.NodeID rand.Read(id[:]) - peer := pm.newPeer(version, NetworkId, p2p.NewPeer(id, name, nil), net) + peer := pm.newPeer(version, p2p.NewPeer(id, name, nil), net) // Start the peer on a new thread errc := make(chan error, 1) diff --git a/eth/peer.go b/eth/peer.go index 695e910f6..15ba22ff5 100644 --- a/eth/peer.go +++ b/eth/peer.go @@ -44,38 +44,51 @@ const ( handshakeTimeout = 5 * time.Second ) -type peer struct { - *p2p.Peer +// PeerInfo represents a short summary of the Ethereum sub-protocol metadata known +// about a connected peer. +type PeerInfo struct { + Version int `json:"version"` // Ethereum protocol version negotiated + Difficulty *big.Int `json:"difficulty"` // Total difficulty of the peer's blockchain + Head string `json:"head"` // SHA3 hash of the peer's best owned block +} +type peer struct { + id string + + *p2p.Peer rw p2p.MsgReadWriter version int // Protocol version negotiated - network int // Network ID being on - - id string - - head common.Hash - td *big.Int - lock sync.RWMutex + head common.Hash + td *big.Int + lock sync.RWMutex knownTxs *set.Set // Set of transaction hashes known to be known by this peer knownBlocks *set.Set // Set of block hashes known to be known by this peer } -func newPeer(version, network int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer { +func newPeer(version int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer { id := p.ID() return &peer{ Peer: p, rw: rw, version: version, - network: network, id: fmt.Sprintf("%x", id[:8]), knownTxs: set.New(), knownBlocks: set.New(), } } +// Info gathers and returns a collection of metadata known about a peer. +func (p *peer) Info() *PeerInfo { + return &PeerInfo{ + Version: p.version, + Difficulty: p.Td(), + Head: fmt.Sprintf("%x", p.Head()), + } +} + // Head retrieves a copy of the current head (most recent) hash of the peer. func (p *peer) Head() (hash common.Hash) { p.lock.RLock() @@ -268,20 +281,22 @@ func (p *peer) RequestReceipts(hashes []common.Hash) error { // Handshake executes the eth protocol handshake, negotiating version number, // network IDs, difficulties, head and genesis blocks. -func (p *peer) Handshake(td *big.Int, head common.Hash, genesis common.Hash) error { +func (p *peer) Handshake(network int, td *big.Int, head common.Hash, genesis common.Hash) error { + // Send out own handshake in a new thread errc := make(chan error, 2) var status statusData // safe to read after two values have been received from errc + go func() { errc <- p2p.Send(p.rw, StatusMsg, &statusData{ ProtocolVersion: uint32(p.version), - NetworkId: uint32(p.network), + NetworkId: uint32(network), TD: td, CurrentBlock: head, GenesisBlock: genesis, }) }() go func() { - errc <- p.readStatus(&status, genesis) + errc <- p.readStatus(network, &status, genesis) }() timeout := time.NewTimer(handshakeTimeout) defer timeout.Stop() @@ -299,7 +314,7 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, genesis common.Hash) err return nil } -func (p *peer) readStatus(status *statusData, genesis common.Hash) (err error) { +func (p *peer) readStatus(network int, status *statusData, genesis common.Hash) (err error) { msg, err := p.rw.ReadMsg() if err != nil { return err @@ -317,8 +332,8 @@ func (p *peer) readStatus(status *statusData, genesis common.Hash) (err error) { if status.GenesisBlock != genesis { return errResp(ErrGenesisBlockMismatch, "%x (!= %x)", status.GenesisBlock, genesis) } - if int(status.NetworkId) != p.network { - return errResp(ErrNetworkIdMismatch, "%d (!= %d)", status.NetworkId, p.network) + if int(status.NetworkId) != network { + return errResp(ErrNetworkIdMismatch, "%d (!= %d)", status.NetworkId, network) } if int(status.ProtocolVersion) != p.version { return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", status.ProtocolVersion, p.version) diff --git a/eth/protocol.go b/eth/protocol.go index 410347ed3..808ac0601 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -33,6 +33,9 @@ const ( eth63 = 63 ) +// Official short name of the protocol used during capability negotiation. +var ProtocolName = "eth" + // Supported versions of the eth protocol (first is primary). var ProtocolVersions = []uint{eth63, eth62, eth61} diff --git a/eth/sync_test.go b/eth/sync_test.go index f3a6718ab..afd90c9b6 100644 --- a/eth/sync_test.go +++ b/eth/sync_test.go @@ -40,8 +40,8 @@ func TestFastSyncDisabling(t *testing.T) { // Sync up the two peers io1, io2 := p2p.MsgPipe() - go pmFull.handle(pmFull.newPeer(63, NetworkId, p2p.NewPeer(discover.NodeID{}, "empty", nil), io2)) - go pmEmpty.handle(pmEmpty.newPeer(63, NetworkId, p2p.NewPeer(discover.NodeID{}, "full", nil), io1)) + go pmFull.handle(pmFull.newPeer(63, p2p.NewPeer(discover.NodeID{}, "empty", nil), io2)) + go pmEmpty.handle(pmEmpty.newPeer(63, p2p.NewPeer(discover.NodeID{}, "full", nil), io1)) time.Sleep(250 * time.Millisecond) pmEmpty.synchronise(pmEmpty.peers.BestPeer()) diff --git a/p2p/peer.go b/p2p/peer.go index 1b3b19c79..72ed4069c 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -359,3 +359,49 @@ func (rw *protoRW) ReadMsg() (Msg, error) { return Msg{}, io.EOF } } + +// PeerInfo represents a short summary of the information known about a connected +// peer. Sub-protocol independent fields are contained and initialized here, with +// protocol specifics delegated to all connected sub-protocols. +type PeerInfo struct { + ID string `json:"id"` // Unique node identifier (also the encryption key) + Name string `json:"name"` // Name of the node, including client type, version, OS, custom data + Caps []string `json:"caps"` // Sum-protocols advertised by this particular peer + Network struct { + LocalAddress string `json:"localAddress"` // Local endpoint of the TCP data connection + RemoteAddress string `json:"remoteAddress"` // Remote endpoint of the TCP data connection + } `json:"network"` + Protocols map[string]interface{} `json:"protocols"` // Sub-protocol specific metadata fields +} + +// Info gathers and returns a collection of metadata known about a peer. +func (p *Peer) Info() *PeerInfo { + // Gather the protocol capabilities + var caps []string + for _, cap := range p.Caps() { + caps = append(caps, cap.String()) + } + // Assemble the generic peer metadata + info := &PeerInfo{ + ID: p.ID().String(), + Name: p.Name(), + Caps: caps, + Protocols: make(map[string]interface{}), + } + info.Network.LocalAddress = p.LocalAddr().String() + info.Network.RemoteAddress = p.RemoteAddr().String() + + // Gather all the running protocol infos + for _, proto := range p.running { + protoInfo := interface{}("unknown") + if query := proto.Protocol.PeerInfo; query != nil { + if metadata := query(p.ID()); metadata != nil { + protoInfo = metadata + } else { + protoInfo = "handshake" + } + } + info.Protocols[proto.Name] = protoInfo + } + return info +} diff --git a/p2p/protocol.go b/p2p/protocol.go index ac0c3d942..ee747ba23 100644 --- a/p2p/protocol.go +++ b/p2p/protocol.go @@ -16,7 +16,11 @@ package p2p -import "fmt" +import ( + "fmt" + + "github.com/ethereum/go-ethereum/p2p/discover" +) // Protocol represents a P2P subprotocol implementation. type Protocol struct { @@ -39,6 +43,15 @@ type Protocol struct { // any protocol-level error (such as an I/O error) that is // encountered. Run func(peer *Peer, rw MsgReadWriter) error + + // NodeInfo is an optional helper method to retrieve protocol specific metadata + // about the host node. + NodeInfo func() interface{} + + // PeerInfo is an optional helper method to retrieve protocol specific metadata + // about a certain peer in the network. If an info retrieval function is set, + // but returns nil, it is assumed that the protocol handshake is still running. + PeerInfo func(id discover.NodeID) interface{} } func (p Protocol) cap() Cap { diff --git a/p2p/server.go b/p2p/server.go index 6060adc71..ee670b10e 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -689,3 +689,66 @@ func (srv *Server) runPeer(p *Peer) { NumConnections: srv.PeerCount(), }) } + +// NodeInfo represents a short summary of the information known about the host. +type NodeInfo struct { + ID string `json:"id"` // Unique node identifier (also the encryption key) + Name string `json:"name"` // Name of the node, including client type, version, OS, custom data + Enode string `json:"enode"` // Enode URL for adding this peer from remote peers + IP string `json:"ip"` // IP address of the node + Ports struct { + Discovery int `json:"discovery"` // UDP listening port for discovery protocol + Listener int `json:"listener"` // TCP listening port for RLPx + } `json:"ports"` + ListenAddr string `json:"listenAddr"` + Protocols map[string]interface{} `json:"protocols"` +} + +// Info gathers and returns a collection of metadata known about the host. +func (srv *Server) NodeInfo() *NodeInfo { + node := srv.Self() + + // Gather and assemble the generic node infos + info := &NodeInfo{ + Name: srv.Name, + Enode: node.String(), + ID: node.ID.String(), + IP: node.IP.String(), + ListenAddr: srv.ListenAddr, + Protocols: make(map[string]interface{}), + } + info.Ports.Discovery = int(node.UDP) + info.Ports.Listener = int(node.TCP) + + // Gather all the running protocol infos (only once per protocol type) + for _, proto := range srv.Protocols { + if _, ok := info.Protocols[proto.Name]; !ok { + nodeInfo := interface{}("unknown") + if query := proto.NodeInfo; query != nil { + nodeInfo = proto.NodeInfo() + } + info.Protocols[proto.Name] = nodeInfo + } + } + return info +} + +// PeersInfo returns an array of metadata objects describing connected peers. +func (srv *Server) PeersInfo() []*PeerInfo { + // Gather all the generic and sub-protocol specific infos + infos := make([]*PeerInfo, 0, srv.PeerCount()) + for _, peer := range srv.Peers() { + if peer != nil { + infos = append(infos, peer.Info()) + } + } + // Sort the result array alphabetically by node identifier + for i := 0; i < len(infos); i++ { + for j := i + 1; j < len(infos); j++ { + if infos[i].ID > infos[j].ID { + infos[i], infos[j] = infos[j], infos[i] + } + } + } + return infos +} diff --git a/rpc/api/admin.go b/rpc/api/admin.go index eb08fbc5d..b359d52a1 100644 --- a/rpc/api/admin.go +++ b/rpc/api/admin.go @@ -32,6 +32,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc/codec" "github.com/ethereum/go-ethereum/rpc/comms" @@ -80,15 +81,17 @@ type adminhandler func(*adminApi, *shared.Request) (interface{}, error) // admin api provider type adminApi struct { xeth *xeth.XEth + network *p2p.Server ethereum *eth.Ethereum codec codec.Codec coder codec.ApiCoder } // create a new admin api instance -func NewAdminApi(xeth *xeth.XEth, ethereum *eth.Ethereum, codec codec.Codec) *adminApi { +func NewAdminApi(xeth *xeth.XEth, network *p2p.Server, ethereum *eth.Ethereum, codec codec.Codec) *adminApi { return &adminApi{ xeth: xeth, + network: network, ethereum: ethereum, codec: codec, coder: codec.New(nil), @@ -137,11 +140,11 @@ func (self *adminApi) AddPeer(req *shared.Request) (interface{}, error) { } func (self *adminApi) Peers(req *shared.Request) (interface{}, error) { - return self.ethereum.PeersInfo(), nil + return self.network.PeersInfo(), nil } func (self *adminApi) NodeInfo(req *shared.Request) (interface{}, error) { - return self.ethereum.NodeInfo(), nil + return self.network.NodeInfo(), nil } func (self *adminApi) DataDir(req *shared.Request) (interface{}, error) { diff --git a/rpc/api/utils.go b/rpc/api/utils.go index 5a3ade46b..6dde4022b 100644 --- a/rpc/api/utils.go +++ b/rpc/api/utils.go @@ -165,7 +165,7 @@ func ParseApiString(apistr string, codec codec.Codec, xeth *xeth.XEth, eth *eth. for i, name := range names { switch strings.ToLower(strings.TrimSpace(name)) { case shared.AdminApiName: - apis[i] = NewAdminApi(xeth, eth, codec) + apis[i] = NewAdminApi(xeth, eth.Network(), eth, codec) case shared.DebugApiName: apis[i] = NewDebugApi(xeth, eth, codec) case shared.DbApiName: