diff --git a/cmd/geth/misccmd.go b/cmd/geth/misccmd.go index aa9b1ee568..c38744efe9 100644 --- a/cmd/geth/misccmd.go +++ b/cmd/geth/misccmd.go @@ -113,7 +113,6 @@ func version(ctx *cli.Context) error { fmt.Println("Git Commit:", gitCommit) } fmt.Println("Architecture:", runtime.GOARCH) - fmt.Println("Protocol Versions:", eth.ProtocolVersions) fmt.Println("Network Id:", eth.DefaultConfig.NetworkId) fmt.Println("Go Version:", runtime.Version()) fmt.Println("Operating System:", runtime.GOOS) diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 2bdad9092a..a7be794cc0 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -683,3 +683,8 @@ func (c *Clique) APIs(chain consensus.ChainReader) []rpc.API { Public: false, }} } + +// Protocol implements consensus.Engine.Protocol +func (c *Clique) Protocol() consensus.Protocol { + return consensus.EthProtocol +} diff --git a/consensus/consensus.go b/consensus/consensus.go index be5e661c12..1e013d5d4c 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -21,6 +21,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" "math/big" @@ -95,6 +96,21 @@ type Engine interface { // APIs returns the RPC APIs this consensus engine provides. APIs(chain ChainReader) []rpc.API + + // Protocol returns the protocol for this consensus + Protocol() Protocol +} + +// Handler should be implemented is the consensus needs to handle and send peer's message +type Handler interface { + // NewChainHead handles a new head block comes + NewChainHead() error + + // HandleMsg handles a message from peer + HandleMsg(address common.Address, data p2p.Msg) (bool, error) + + // SetBroadcaster sets the broadcaster to send message to peers + SetBroadcaster(Broadcaster) } // PoW is a consensus engine based on proof-of-work. diff --git a/consensus/ethash/ethash.go b/consensus/ethash/ethash.go index 1b3dcee302..5d5406ebed 100644 --- a/consensus/ethash/ethash.go +++ b/consensus/ethash/ethash.go @@ -576,3 +576,8 @@ func (ethash *Ethash) APIs(chain consensus.ChainReader) []rpc.API { func SeedHash(block uint64) []byte { return seedHash(block) } + +// Protocol implements consensus.Engine.Protocol +func (ethash *Ethash) Protocol() consensus.Protocol { + return consensus.EthProtocol +} diff --git a/consensus/protocol.go b/consensus/protocol.go new file mode 100644 index 0000000000..64181da693 --- /dev/null +++ b/consensus/protocol.go @@ -0,0 +1,61 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package consensus implements different Ethereum consensus engines. +package consensus + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// Constants to match up protocol versions and messages +const ( + Eth62 = 62 + Eth63 = 63 +) + +var ( + EthProtocol = Protocol{ + Name: "eth", + Versions: []uint{Eth62, Eth63}, + Lengths: []uint64{17, 8}, + } +) + +// Protocol defines the protocol of the consensus +type Protocol struct { + // Official short name of the protocol used during capability negotiation. + Name string + // Supported versions of the eth protocol (first is primary). + Versions []uint + // Number of implemented message corresponding to different protocol versions. + Lengths []uint64 +} + +// Broadcaster defines the interface to enqueue blocks to fetcher and find peer +type Broadcaster interface { + // Enqueue add a block into fetcher queue + Enqueue(id string, block *types.Block) + // FindPeers retrives peers by addresses + FindPeers(map[common.Address]bool) map[common.Address]Peer +} + +// Peer defines the interface to communicate with peer +type Peer interface { + // Send sends the message to this peer + Send(msgcode uint64, data interface{}) error +} diff --git a/eth/backend.go b/eth/backend.go index 94aad23101..2359f3ec30 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -135,7 +135,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { bloomIndexer: NewBloomIndexer(chainDb, params.BloomBitsBlocks), } - log.Info("Initialising Ethereum protocol", "versions", ProtocolVersions, "network", config.NetworkId) + log.Info("Initialising Ethereum protocol", "versions", eth.engine.Protocol().Versions, "network", config.NetworkId) if !config.SkipBcVersionCheck { bcVersion := core.GetBlockChainVersion(chainDb) diff --git a/eth/handler.go b/eth/handler.go index 3fae0cd00d..b04a52c038 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/fetcher" "github.com/ethereum/go-ethereum/ethdb" @@ -94,6 +95,8 @@ type ProtocolManager struct { // wait group is used for graceful shutdowns during downloading // and processing wg sync.WaitGroup + + engine consensus.Engine } // NewProtocolManager returns a new ethereum sub protocol manager. The Ethereum sub protocol manages peers capable @@ -111,7 +114,13 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne noMorePeers: make(chan struct{}), txsyncCh: make(chan *txsync), quitSync: make(chan struct{}), + engine: engine, } + + if handler, ok := manager.engine.(consensus.Handler); ok { + handler.SetBroadcaster(manager) + } + // Figure out whether to allow fast sync or not if mode == downloader.FastSync && blockchain.CurrentBlock().NumberU64() > 0 { log.Warn("Blockchain not empty, fast sync disabled") @@ -120,19 +129,20 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne if mode == downloader.FastSync { manager.fastSync = uint32(1) } + protocol := engine.Protocol() // Initiate a sub-protocol for every implemented version we can handle - manager.SubProtocols = make([]p2p.Protocol, 0, len(ProtocolVersions)) - for i, version := range ProtocolVersions { + manager.SubProtocols = make([]p2p.Protocol, 0, len(protocol.Versions)) + for i, version := range protocol.Versions { // Skip protocol version if incompatible with the mode of operation - if mode == downloader.FastSync && version < eth63 { + if mode == downloader.FastSync && version < consensus.Eth63 { continue } // Compatible; initialise the sub-protocol version := version // Closure for the run manager.SubProtocols = append(manager.SubProtocols, p2p.Protocol{ - Name: ProtocolName, + Name: protocol.Name, Version: version, - Length: ProtocolLengths[i], + Length: protocol.Lengths[i], Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { peer := manager.newPeer(int(version), p, rw) select { @@ -326,6 +336,18 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { } defer msg.Discard() + if handler, ok := pm.engine.(consensus.Handler); ok { + pubKey, err := p.ID().Pubkey() + if err != nil { + return err + } + addr := crypto.PubkeyToAddress(*pubKey) + handled, err := handler.HandleMsg(addr, msg) + if handled { + return err + } + } + // Handle the message depending on its contents switch { case msg.Code == StatusMsg: @@ -517,7 +539,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { } } - case p.version >= eth63 && msg.Code == GetNodeDataMsg: + case p.version >= consensus.Eth63 && msg.Code == GetNodeDataMsg: // Decode the retrieval message msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size)) if _, err := msgStream.List(); err != nil { @@ -544,7 +566,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { } return p.SendNodeData(data) - case p.version >= eth63 && msg.Code == NodeDataMsg: + case p.version >= consensus.Eth63 && msg.Code == NodeDataMsg: // A batch of node state data arrived to one of our previous requests var data [][]byte if err := msg.Decode(&data); err != nil { @@ -555,7 +577,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { log.Debug("Failed to deliver node state data", "err", err) } - case p.version >= eth63 && msg.Code == GetReceiptsMsg: + case p.version >= consensus.Eth63 && msg.Code == GetReceiptsMsg: // Decode the retrieval message msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size)) if _, err := msgStream.List(); err != nil { @@ -591,7 +613,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { } return p.SendReceiptsRLP(receipts) - case p.version >= eth63 && msg.Code == ReceiptsMsg: + case p.version >= consensus.Eth63 && msg.Code == ReceiptsMsg: // A batch of receipts arrived to one of our previous requests var receipts [][]*types.Receipt if err := msg.Decode(&receipts); err != nil { @@ -679,6 +701,10 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { return nil } +func (pm *ProtocolManager) Enqueue(id string, block *types.Block) { + pm.fetcher.Enqueue(id, block) +} + // BroadcastBlock will either propagate a block to a subset of it's peers, or // will only announce it's availability (depending what's requested). func (pm *ProtocolManager) BroadcastBlock(block *types.Block, propagate bool) { @@ -770,3 +796,18 @@ func (self *ProtocolManager) NodeInfo() *NodeInfo { Head: currentBlock.Hash(), } } + +func (self *ProtocolManager) FindPeers(targets map[common.Address]bool) map[common.Address]consensus.Peer { + m := make(map[common.Address]consensus.Peer) + for _, p := range self.peers.Peers() { + pubKey, err := p.ID().Pubkey() + if err != nil { + continue + } + addr := crypto.PubkeyToAddress(*pubKey) + if targets[addr] { + m[addr] = p + } + } + return m +} diff --git a/eth/handler_test.go b/eth/handler_test.go index e336dfa285..5f6c8d9672 100644 --- a/eth/handler_test.go +++ b/eth/handler_test.go @@ -24,6 +24,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" @@ -49,12 +50,12 @@ func TestProtocolCompatibility(t *testing.T) { {61, downloader.FastSync, false}, {62, downloader.FastSync, false}, {63, downloader.FastSync, true}, } // Make sure anything we screw up is restored - backup := ProtocolVersions - defer func() { ProtocolVersions = backup }() + backup := consensus.EthProtocol.Versions + defer func() { consensus.EthProtocol.Versions = backup }() // Try all available compatibility configs and check for errors for i, tt := range tests { - ProtocolVersions = []uint{tt.version} + consensus.EthProtocol.Versions = []uint{tt.version} pm, _, err := newTestProtocolManager(tt.mode, 0, nil, nil) if pm != nil { @@ -482,7 +483,7 @@ func testDAOChallenge(t *testing.T, localForked, remoteForked bool, timeout bool defer pm.Stop() // Connect a new peer and check that we receive the DAO challenge - peer, _ := newTestPeer("peer", eth63, pm, true) + peer, _ := newTestPeer("peer", consensus.Eth63, pm, true) defer peer.close() challenge := &getBlockHeadersData{ diff --git a/eth/metrics.go b/eth/metrics.go index 0533a2a875..79c78e5a89 100644 --- a/eth/metrics.go +++ b/eth/metrics.go @@ -17,6 +17,7 @@ package eth import ( + "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/p2p" ) @@ -92,9 +93,9 @@ func (rw *meteredMsgReadWriter) ReadMsg() (p2p.Msg, error) { case msg.Code == BlockBodiesMsg: packets, traffic = reqBodyInPacketsMeter, reqBodyInTrafficMeter - case rw.version >= eth63 && msg.Code == NodeDataMsg: + case rw.version >= consensus.Eth63 && msg.Code == NodeDataMsg: packets, traffic = reqStateInPacketsMeter, reqStateInTrafficMeter - case rw.version >= eth63 && msg.Code == ReceiptsMsg: + case rw.version >= consensus.Eth63 && msg.Code == ReceiptsMsg: packets, traffic = reqReceiptInPacketsMeter, reqReceiptInTrafficMeter case msg.Code == NewBlockHashesMsg: @@ -119,9 +120,9 @@ func (rw *meteredMsgReadWriter) WriteMsg(msg p2p.Msg) error { case msg.Code == BlockBodiesMsg: packets, traffic = reqBodyOutPacketsMeter, reqBodyOutTrafficMeter - case rw.version >= eth63 && msg.Code == NodeDataMsg: + case rw.version >= consensus.Eth63 && msg.Code == NodeDataMsg: packets, traffic = reqStateOutPacketsMeter, reqStateOutTrafficMeter - case rw.version >= eth63 && msg.Code == ReceiptsMsg: + case rw.version >= consensus.Eth63 && msg.Code == ReceiptsMsg: packets, traffic = reqReceiptOutPacketsMeter, reqReceiptOutTrafficMeter case msg.Code == NewBlockHashesMsg: diff --git a/eth/peer.go b/eth/peer.go index 42ead53965..a4a0b5ee95 100644 --- a/eth/peer.go +++ b/eth/peer.go @@ -130,6 +130,12 @@ func (p *peer) MarkTransaction(hash common.Hash) { p.knownTxs.Add(hash) } +// Send writes an RLP-encoded message with the given code. +// data should encode as an RLP list. +func (p *peer) Send(msgcode uint64, data interface{}) error { + return p2p.Send(p.rw, msgcode, data) +} + // SendTransactions sends transactions to the peer and includes the hashes // in its transaction hash set for future reference. func (p *peer) SendTransactions(txs types.Transactions) error { @@ -341,6 +347,18 @@ func (ps *peerSet) Unregister(id string) error { return nil } +// Peers returns all registered peers +func (ps *peerSet) Peers() map[string]*peer { + ps.lock.RLock() + defer ps.lock.RUnlock() + + set := make(map[string]*peer) + for id, p := range ps.peers { + set[id] = p + } + return set +} + // Peer retrieves the registered peer with the given id. func (ps *peerSet) Peer(id string) *peer { ps.lock.RLock() diff --git a/eth/protocol.go b/eth/protocol.go index cd7db57f23..81ab872758 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -28,21 +28,6 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) -// Constants to match up protocol versions and messages -const ( - eth62 = 62 - 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} - -// Number of implemented message corresponding to different protocol versions. -var ProtocolLengths = []uint64{17, 8} - const ProtocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message // eth protocol message codes diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index ae7e252654..a91a221d5d 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -373,9 +373,10 @@ func (s *Service) login(conn *websocket.Conn) error { infos := s.server.NodeInfo() var network, protocol string - if info := infos.Protocols["eth"]; info != nil { + p := s.engine.Protocol() + if info := infos.Protocols[p.Name]; info != nil { network = fmt.Sprintf("%d", info.(*eth.NodeInfo).Network) - protocol = fmt.Sprintf("eth/%d", eth.ProtocolVersions[0]) + protocol = fmt.Sprintf("%s/%d", p.Name, p.Versions[0]) } else { network = fmt.Sprintf("%d", infos.Protocols["les"].(*les.NodeInfo).Network) protocol = fmt.Sprintf("les/%d", les.ClientProtocolVersions[0])