forked from mirror/go-ethereum
parent
2339ee9910
commit
c2f410214c
@ -0,0 +1,224 @@ |
||||
package eth |
||||
|
||||
import ( |
||||
"fmt" |
||||
"sync" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/eth/downloader" |
||||
"github.com/ethereum/go-ethereum/logger" |
||||
"github.com/ethereum/go-ethereum/logger/glog" |
||||
"github.com/ethereum/go-ethereum/p2p" |
||||
"github.com/ethereum/go-ethereum/rlp" |
||||
) |
||||
|
||||
func errResp(code errCode, format string, v ...interface{}) error { |
||||
return fmt.Errorf("%v - %v", code, fmt.Sprintf(format, v...)) |
||||
} |
||||
|
||||
// main entrypoint, wrappers starting a server running the eth protocol
|
||||
// use this constructor to attach the protocol ("class") to server caps
|
||||
// the Dev p2p layer then runs the protocol instance on each peer
|
||||
func EthProtocol(protocolVersion, networkId int, txPool txPool, chainManager chainManager, downloader *downloader.Downloader) p2p.Protocol { |
||||
protocol := newProtocolManager(txPool, chainManager, downloader) |
||||
|
||||
return p2p.Protocol{ |
||||
Name: "eth", |
||||
Version: uint(protocolVersion), |
||||
Length: ProtocolLength, |
||||
Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { |
||||
//return runEthProtocol(protocolVersion, networkId, txPool, chainManager, downloader, p, rw)
|
||||
peer := protocol.newPeer(protocolVersion, networkId, p, rw) |
||||
err := protocol.handle(peer) |
||||
glog.V(logger.Detail).Infof("[%s]: %v\n", peer.id, err) |
||||
|
||||
return err |
||||
}, |
||||
} |
||||
} |
||||
|
||||
type hashFetcherFn func(common.Hash) error |
||||
type blockFetcherFn func([]common.Hash) error |
||||
|
||||
// extProt is an interface which is passed around so we can expose GetHashes and GetBlock without exposing it to the rest of the protocol
|
||||
// extProt is passed around to peers which require to GetHashes and GetBlocks
|
||||
type extProt struct { |
||||
getHashes hashFetcherFn |
||||
getBlocks blockFetcherFn |
||||
} |
||||
|
||||
func (ep extProt) GetHashes(hash common.Hash) error { return ep.getHashes(hash) } |
||||
func (ep extProt) GetBlock(hashes []common.Hash) error { return ep.getBlocks(hashes) } |
||||
|
||||
type EthProtocolManager struct { |
||||
protVer, netId int |
||||
txpool txPool |
||||
chainman chainManager |
||||
downloader *downloader.Downloader |
||||
|
||||
pmu sync.Mutex |
||||
peers map[string]*peer |
||||
} |
||||
|
||||
func newProtocolManager(txpool txPool, chainman chainManager, downloader *downloader.Downloader) *EthProtocolManager { |
||||
return &EthProtocolManager{ |
||||
txpool: txpool, |
||||
chainman: chainman, |
||||
downloader: downloader, |
||||
peers: make(map[string]*peer), |
||||
} |
||||
} |
||||
|
||||
func (pm *EthProtocolManager) newPeer(pv, nv int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer { |
||||
pm.pmu.Lock() |
||||
defer pm.pmu.Unlock() |
||||
|
||||
td, current, genesis := pm.chainman.Status() |
||||
|
||||
peer := newPeer(pv, nv, genesis, current, td, p, rw) |
||||
pm.peers[peer.id] = peer |
||||
|
||||
return peer |
||||
} |
||||
|
||||
func (pm *EthProtocolManager) handle(p *peer) error { |
||||
if err := p.handleStatus(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
pm.downloader.RegisterPeer(p.id, p.td, p.currentHash, p.requestHashes, p.requestBlocks) |
||||
defer pm.downloader.UnregisterPeer(p.id) |
||||
|
||||
// propagate existing transactions. new transactions appearing
|
||||
// after this will be sent via broadcasts.
|
||||
if err := p.sendTransactions(pm.txpool.GetTransactions()); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// main loop. handle incoming messages.
|
||||
for { |
||||
if err := pm.handleMsg(p); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (self *EthProtocolManager) handleMsg(p *peer) error { |
||||
msg, err := p.rw.ReadMsg() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if msg.Size > ProtocolMaxMsgSize { |
||||
return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, ProtocolMaxMsgSize) |
||||
} |
||||
// make sure that the payload has been fully consumed
|
||||
defer msg.Discard() |
||||
|
||||
switch msg.Code { |
||||
case GetTxMsg: // ignore
|
||||
case StatusMsg: |
||||
return errResp(ErrExtraStatusMsg, "uncontrolled status message") |
||||
|
||||
case TxMsg: |
||||
// TODO: rework using lazy RLP stream
|
||||
var txs []*types.Transaction |
||||
if err := msg.Decode(&txs); err != nil { |
||||
return errResp(ErrDecode, "msg %v: %v", msg, err) |
||||
} |
||||
for i, tx := range txs { |
||||
if tx == nil { |
||||
return errResp(ErrDecode, "transaction %d is nil", i) |
||||
} |
||||
jsonlogger.LogJson(&logger.EthTxReceived{ |
||||
TxHash: tx.Hash().Hex(), |
||||
RemoteId: p.ID().String(), |
||||
}) |
||||
} |
||||
self.txpool.AddTransactions(txs) |
||||
|
||||
case GetBlockHashesMsg: |
||||
var request getBlockHashesMsgData |
||||
if err := msg.Decode(&request); err != nil { |
||||
return errResp(ErrDecode, "->msg %v: %v", msg, err) |
||||
} |
||||
|
||||
if request.Amount > maxHashes { |
||||
request.Amount = maxHashes |
||||
} |
||||
hashes := self.chainman.GetBlockHashesFromHash(request.Hash, request.Amount) |
||||
return p.sendBlockHashes(hashes) |
||||
case BlockHashesMsg: |
||||
msgStream := rlp.NewStream(msg.Payload) |
||||
|
||||
var hashes []common.Hash |
||||
if err := msgStream.Decode(&hashes); err != nil { |
||||
break |
||||
} |
||||
self.downloader.HashCh <- hashes |
||||
|
||||
case GetBlocksMsg: |
||||
msgStream := rlp.NewStream(msg.Payload) |
||||
if _, err := msgStream.List(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
var blocks []*types.Block |
||||
var i int |
||||
for { |
||||
i++ |
||||
var hash common.Hash |
||||
err := msgStream.Decode(&hash) |
||||
if err == rlp.EOL { |
||||
break |
||||
} else if err != nil { |
||||
return errResp(ErrDecode, "msg %v: %v", msg, err) |
||||
} |
||||
|
||||
block := self.chainman.GetBlock(hash) |
||||
if block != nil { |
||||
blocks = append(blocks, block) |
||||
} |
||||
if i == maxBlocks { |
||||
break |
||||
} |
||||
} |
||||
return p.sendBlocks(blocks) |
||||
case BlocksMsg: |
||||
msgStream := rlp.NewStream(msg.Payload) |
||||
|
||||
var blocks []*types.Block |
||||
if err := msgStream.Decode(&blocks); err != nil { |
||||
glog.V(logger.Detail).Infoln("Decode error", err) |
||||
fmt.Println("decode error", err) |
||||
blocks = nil |
||||
} |
||||
self.downloader.DeliverChunk(p.id, blocks) |
||||
|
||||
case NewBlockMsg: |
||||
var request newBlockMsgData |
||||
if err := msg.Decode(&request); err != nil { |
||||
return errResp(ErrDecode, "%v: %v", msg, err) |
||||
} |
||||
if err := request.Block.ValidateFields(); err != nil { |
||||
return errResp(ErrDecode, "block validation %v: %v", msg, err) |
||||
} |
||||
hash := request.Block.Hash() |
||||
_, chainHead, _ := self.chainman.Status() |
||||
|
||||
jsonlogger.LogJson(&logger.EthChainReceivedNewBlock{ |
||||
BlockHash: hash.Hex(), |
||||
BlockNumber: request.Block.Number(), // this surely must be zero
|
||||
ChainHeadHash: chainHead.Hex(), |
||||
BlockPrevHash: request.Block.ParentHash().Hex(), |
||||
RemoteId: p.ID().String(), |
||||
}) |
||||
self.downloader.AddBlock(p.id, request.Block, request.TD) |
||||
|
||||
default: |
||||
return errResp(ErrInvalidMsgCode, "%v", msg.Code) |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,137 @@ |
||||
package eth |
||||
|
||||
import ( |
||||
"fmt" |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/p2p" |
||||
"gopkg.in/fatih/set.v0" |
||||
) |
||||
|
||||
type statusMsgData struct { |
||||
ProtocolVersion uint32 |
||||
NetworkId uint32 |
||||
TD *big.Int |
||||
CurrentBlock common.Hash |
||||
GenesisBlock common.Hash |
||||
} |
||||
|
||||
type getBlockHashesMsgData struct { |
||||
Hash common.Hash |
||||
Amount uint64 |
||||
} |
||||
|
||||
type peer struct { |
||||
*p2p.Peer |
||||
|
||||
rw p2p.MsgReadWriter |
||||
|
||||
protv, netid int |
||||
|
||||
currentHash common.Hash |
||||
id string |
||||
td *big.Int |
||||
|
||||
genesis, ourHash common.Hash |
||||
ourTd *big.Int |
||||
|
||||
txHashes *set.Set |
||||
blockHashes *set.Set |
||||
} |
||||
|
||||
func newPeer(protv, netid int, genesis, currentHash common.Hash, td *big.Int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer { |
||||
id := p.ID() |
||||
|
||||
return &peer{ |
||||
Peer: p, |
||||
rw: rw, |
||||
genesis: genesis, |
||||
ourHash: currentHash, |
||||
ourTd: td, |
||||
protv: protv, |
||||
netid: netid, |
||||
id: fmt.Sprintf("%x", id[:8]), |
||||
txHashes: set.New(), |
||||
blockHashes: set.New(), |
||||
} |
||||
} |
||||
|
||||
// sendTransactions sends transactions to the peer and includes the hashes
|
||||
// in it's tx hash set for future reference. The tx hash will allow the
|
||||
// manager to check whether the peer has already received this particular
|
||||
// transaction
|
||||
func (p *peer) sendTransactions(txs types.Transactions) error { |
||||
for _, tx := range txs { |
||||
p.txHashes.Add(tx.Hash()) |
||||
} |
||||
|
||||
return p2p.Send(p.rw, TxMsg, txs) |
||||
} |
||||
|
||||
func (p *peer) sendBlockHashes(hashes []common.Hash) error { |
||||
return p2p.Send(p.rw, BlockHashesMsg, hashes) |
||||
} |
||||
|
||||
func (p *peer) sendBlocks(blocks []*types.Block) error { |
||||
return p2p.Send(p.rw, BlocksMsg, blocks) |
||||
} |
||||
|
||||
func (p *peer) requestHashes(from common.Hash) error { |
||||
p.Debugf("fetching hashes (%d) %x...\n", maxHashes, from[0:4]) |
||||
return p2p.Send(p.rw, GetBlockHashesMsg, getBlockHashesMsgData{from, maxHashes}) |
||||
} |
||||
|
||||
func (p *peer) requestBlocks(hashes []common.Hash) error { |
||||
p.Debugf("fetching %v blocks", len(hashes)) |
||||
return p2p.Send(p.rw, GetBlocksMsg, hashes) |
||||
} |
||||
|
||||
func (p *peer) handleStatus() error { |
||||
errc := make(chan error, 1) |
||||
go func() { |
||||
errc <- p2p.Send(p.rw, StatusMsg, &statusMsgData{ |
||||
ProtocolVersion: uint32(p.protv), |
||||
NetworkId: uint32(p.netid), |
||||
TD: p.ourTd, |
||||
CurrentBlock: p.ourHash, |
||||
GenesisBlock: p.genesis, |
||||
}) |
||||
}() |
||||
|
||||
// read and handle remote status
|
||||
msg, err := p.rw.ReadMsg() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if msg.Code != StatusMsg { |
||||
return errResp(ErrNoStatusMsg, "first msg has code %x (!= %x)", msg.Code, StatusMsg) |
||||
} |
||||
if msg.Size > ProtocolMaxMsgSize { |
||||
return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, ProtocolMaxMsgSize) |
||||
} |
||||
|
||||
var status statusMsgData |
||||
if err := msg.Decode(&status); err != nil { |
||||
return errResp(ErrDecode, "msg %v: %v", msg, err) |
||||
} |
||||
|
||||
if status.GenesisBlock != p.genesis { |
||||
return errResp(ErrGenesisBlockMismatch, "%x (!= %x)", status.GenesisBlock, p.genesis) |
||||
} |
||||
|
||||
if int(status.NetworkId) != p.netid { |
||||
return errResp(ErrNetworkIdMismatch, "%d (!= %d)", status.NetworkId, p.netid) |
||||
} |
||||
|
||||
if int(status.ProtocolVersion) != p.protv { |
||||
return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", status.ProtocolVersion, p.protv) |
||||
} |
||||
// Set the total difficulty of the peer
|
||||
p.td = status.TD |
||||
// set the best hash of the peer
|
||||
p.currentHash = status.CurrentBlock |
||||
|
||||
return <-errc |
||||
} |
Loading…
Reference in new issue