les: split handshake

les, core: introduce forkid for les4

les: update peer unit test

les: check peer version in handler

les: fix linter

les: address comments
This commit is contained in:
rjl493456442 2019-10-30 20:21:29 +08:00
parent 23c8c74131
commit 05178a56c9
6 changed files with 304 additions and 366 deletions

View file

@ -27,7 +27,7 @@ import (
"strings" "strings"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
@ -44,6 +44,18 @@ var (
ErrLocalIncompatibleOrStale = errors.New("local incompatible or needs update") ErrLocalIncompatibleOrStale = errors.New("local incompatible or needs update")
) )
// Blockchain defines all necessary method to build a forkID.
type Blockchain interface {
// Config retrieves the chain's fork configuration.
Config() *params.ChainConfig
// Genesis retrieves the chain's genesis block.
Genesis() *types.Block
// CurrentHeader retrieves the current head header of the canonical chain.
CurrentHeader() *types.Header
}
// ID is a fork identifier as defined by EIP-2124. // ID is a fork identifier as defined by EIP-2124.
type ID struct { type ID struct {
Hash [4]byte // CRC32 checksum of the genesis block and passed fork block numbers Hash [4]byte // CRC32 checksum of the genesis block and passed fork block numbers
@ -54,7 +66,7 @@ type ID struct {
type Filter func(id ID) error type Filter func(id ID) error
// NewID calculates the Ethereum fork ID from the chain config and head. // NewID calculates the Ethereum fork ID from the chain config and head.
func NewID(chain *core.BlockChain) ID { func NewID(chain Blockchain) ID {
return newID( return newID(
chain.Config(), chain.Config(),
chain.Genesis().Hash(), chain.Genesis().Hash(),
@ -85,7 +97,7 @@ func newID(config *params.ChainConfig, genesis common.Hash, head uint64) ID {
// NewFilter creates a filter that returns if a fork ID should be rejected or not // NewFilter creates a filter that returns if a fork ID should be rejected or not
// based on the local chain's status. // based on the local chain's status.
func NewFilter(chain *core.BlockChain) Filter { func NewFilter(chain Blockchain) Filter {
return newFilter( return newFilter(
chain.Config(), chain.Config(),
chain.Genesis().Hash(), chain.Genesis().Hash(),

View file

@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/core/forkid"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/light"
@ -35,6 +36,7 @@ import (
// responses. // responses.
type clientHandler struct { type clientHandler struct {
ulc *ulc ulc *ulc
forkFilter forkid.Filter
checkpoint *params.TrustedCheckpoint checkpoint *params.TrustedCheckpoint
fetcher *lightFetcher fetcher *lightFetcher
downloader *downloader.Downloader downloader *downloader.Downloader
@ -47,6 +49,7 @@ type clientHandler struct {
func newClientHandler(ulcServers []string, ulcFraction int, checkpoint *params.TrustedCheckpoint, backend *LightEthereum) *clientHandler { func newClientHandler(ulcServers []string, ulcFraction int, checkpoint *params.TrustedCheckpoint, backend *LightEthereum) *clientHandler {
handler := &clientHandler{ handler := &clientHandler{
forkFilter: forkid.NewFilter(backend.blockchain),
checkpoint: checkpoint, checkpoint: checkpoint,
backend: backend, backend: backend,
closeCh: make(chan struct{}), closeCh: make(chan struct{}),
@ -107,7 +110,7 @@ func (h *clientHandler) handle(p *peer) error {
number = head.Number.Uint64() number = head.Number.Uint64()
td = h.backend.blockchain.GetTd(hash, number) td = h.backend.blockchain.GetTd(hash, number)
) )
if err := p.Handshake(td, hash, number, h.backend.blockchain.Genesis().Hash(), nil); err != nil { if err := p.handshakeWithServer(td, hash, number, h.backend.blockchain.Genesis().Hash(), forkid.NewID(h.backend.blockchain), h.forkFilter); err != nil {
p.Log().Debug("Light Ethereum handshake failed", "err", err) p.Log().Debug("Light Ethereum handshake failed", "err", err)
return err return err
} }
@ -159,8 +162,8 @@ func (h *clientHandler) handleMsg(p *peer) error {
var deliverMsg *Msg var deliverMsg *Msg
// Handle the message depending on its contents // Handle the message depending on its contents
switch msg.Code { switch {
case AnnounceMsg: case msg.Code == AnnounceMsg:
p.Log().Trace("Received announce message") p.Log().Trace("Received announce message")
var req announceData var req announceData
if err := msg.Decode(&req); err != nil { if err := msg.Decode(&req); err != nil {
@ -189,7 +192,7 @@ func (h *clientHandler) handleMsg(p *peer) error {
p.Log().Trace("Announce message content", "number", req.Number, "hash", req.Hash, "td", req.Td, "reorg", req.ReorgDepth) p.Log().Trace("Announce message content", "number", req.Number, "hash", req.Hash, "td", req.Td, "reorg", req.ReorgDepth)
h.fetcher.announce(p, &req) h.fetcher.announce(p, &req)
} }
case BlockHeadersMsg: case msg.Code == BlockHeadersMsg:
p.Log().Trace("Received block header response message") p.Log().Trace("Received block header response message")
var resp struct { var resp struct {
ReqID, BV uint64 ReqID, BV uint64
@ -206,7 +209,7 @@ func (h *clientHandler) handleMsg(p *peer) error {
log.Debug("Failed to deliver headers", "err", err) log.Debug("Failed to deliver headers", "err", err)
} }
} }
case BlockBodiesMsg: case msg.Code == BlockBodiesMsg:
p.Log().Trace("Received block bodies response") p.Log().Trace("Received block bodies response")
var resp struct { var resp struct {
ReqID, BV uint64 ReqID, BV uint64
@ -221,7 +224,7 @@ func (h *clientHandler) handleMsg(p *peer) error {
ReqID: resp.ReqID, ReqID: resp.ReqID,
Obj: resp.Data, Obj: resp.Data,
} }
case CodeMsg: case msg.Code == CodeMsg:
p.Log().Trace("Received code response") p.Log().Trace("Received code response")
var resp struct { var resp struct {
ReqID, BV uint64 ReqID, BV uint64
@ -236,7 +239,7 @@ func (h *clientHandler) handleMsg(p *peer) error {
ReqID: resp.ReqID, ReqID: resp.ReqID,
Obj: resp.Data, Obj: resp.Data,
} }
case ReceiptsMsg: case msg.Code == ReceiptsMsg:
p.Log().Trace("Received receipts response") p.Log().Trace("Received receipts response")
var resp struct { var resp struct {
ReqID, BV uint64 ReqID, BV uint64
@ -251,7 +254,7 @@ func (h *clientHandler) handleMsg(p *peer) error {
ReqID: resp.ReqID, ReqID: resp.ReqID,
Obj: resp.Receipts, Obj: resp.Receipts,
} }
case ProofsV2Msg: case msg.Code == ProofsV2Msg:
p.Log().Trace("Received les/2 proofs response") p.Log().Trace("Received les/2 proofs response")
var resp struct { var resp struct {
ReqID, BV uint64 ReqID, BV uint64
@ -266,7 +269,7 @@ func (h *clientHandler) handleMsg(p *peer) error {
ReqID: resp.ReqID, ReqID: resp.ReqID,
Obj: resp.Data, Obj: resp.Data,
} }
case HelperTrieProofsMsg: case msg.Code == HelperTrieProofsMsg:
p.Log().Trace("Received helper trie proof response") p.Log().Trace("Received helper trie proof response")
var resp struct { var resp struct {
ReqID, BV uint64 ReqID, BV uint64
@ -281,7 +284,7 @@ func (h *clientHandler) handleMsg(p *peer) error {
ReqID: resp.ReqID, ReqID: resp.ReqID,
Obj: resp.Data, Obj: resp.Data,
} }
case TxStatusMsg: case msg.Code == TxStatusMsg:
p.Log().Trace("Received tx status response") p.Log().Trace("Received tx status response")
var resp struct { var resp struct {
ReqID, BV uint64 ReqID, BV uint64
@ -296,11 +299,11 @@ func (h *clientHandler) handleMsg(p *peer) error {
ReqID: resp.ReqID, ReqID: resp.ReqID,
Obj: resp.Status, Obj: resp.Status,
} }
case StopMsg: case msg.Code == StopMsg && p.version >= lpv3:
p.freezeServer(true) p.freezeServer(true)
h.backend.retriever.frozen(p) h.backend.retriever.frozen(p)
p.Log().Debug("Service stopped") p.Log().Debug("Service stopped")
case ResumeMsg: case msg.Code == ResumeMsg && p.version >= lpv3:
var bv uint64 var bv uint64
if err := msg.Decode(&bv); err != nil { if err := msg.Decode(&bv); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err) return errResp(ErrDecode, "msg %v: %v", msg, err)

View file

@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/forkid"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/les/flowcontrol" "github.com/ethereum/go-ethereum/les/flowcontrol"
@ -45,6 +46,11 @@ var (
errNotRegistered = errors.New("peer is not registered") errNotRegistered = errors.New("peer is not registered")
) )
var (
s = rand.NewSource(time.Now().UnixNano())
r = rand.New(s)
)
const ( const (
maxRequestErrors = 20 // number of invalid requests tolerated (makes the protocol less brittle but still avoids spam) maxRequestErrors = 20 // number of invalid requests tolerated (makes the protocol less brittle but still avoids spam)
maxResponseErrors = 50 // number of invalid responses tolerated (makes the protocol less brittle but still avoids spam) maxResponseErrors = 50 // number of invalid responses tolerated (makes the protocol less brittle but still avoids spam)
@ -542,7 +548,7 @@ func (m keyValueMap) get(key string, val interface{}) error {
return rlp.DecodeBytes(enc, val) return rlp.DecodeBytes(enc, val)
} }
func (p *peer) sendReceiveHandshake(sendList keyValueList) (keyValueList, error) { func (p *peer) exchangeHandshake(sendList keyValueList) (keyValueList, error) {
// Send out own handshake in a new thread // Send out own handshake in a new thread
errc := make(chan error, 1) errc := make(chan error, 1)
go func() { go func() {
@ -570,9 +576,11 @@ func (p *peer) sendReceiveHandshake(sendList keyValueList) (keyValueList, error)
return recvList, nil return recvList, nil
} }
// Handshake executes the les protocol handshake, negotiating version number, // handshake executes the les protocol handshake, negotiating version number,
// network IDs, difficulties, head and genesis blocks. // network IDs, difficulties, head and genesis blocks. Besides the basic handshake
func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, server *LesServer) error { // fields, server and client can exchange and resolve some specified fields through
// two callback functions.
func (p *peer) handshake(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter, sendCallback func(*keyValueList), recvCallback func(keyValueMap) error) error {
p.lock.Lock() p.lock.Lock()
defer p.lock.Unlock() defer p.lock.Unlock()
@ -585,54 +593,19 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
send = send.add("headHash", head) send = send.add("headHash", head)
send = send.add("headNum", headNum) send = send.add("headNum", headNum)
send = send.add("genesisHash", genesis) send = send.add("genesisHash", genesis)
if server != nil {
// Add some information which services server can offer.
if !server.config.UltraLightOnlyAnnounce {
send = send.add("serveHeaders", nil)
send = send.add("serveChainSince", uint64(0))
send = send.add("serveStateSince", uint64(0))
// If local ethereum node is running in archive mode, advertise ourselves we have // If the protocol version is beyond les4, then pass the forkID
// all version state data. Otherwise only recent state is available. // as well. Check http://eips.ethereum.org/EIPS/eip-2124 for more
stateRecent := uint64(core.TriesInMemory - 4) // spec detail.
if server.archiveMode { if p.version >= lpv4 {
stateRecent = 0 send = send.add("forkID", forkID)
} }
send = send.add("serveRecentState", stateRecent) // Add client-specified or server-specified fields
send = send.add("txRelay", nil) if sendCallback != nil {
sendCallback(&send)
} }
send = send.add("flowControl/BL", server.defParams.BufLimit) // Exchange the handshake packet and resolve the received one.
send = send.add("flowControl/MRR", server.defParams.MinRecharge) recvList, err := p.exchangeHandshake(send)
var costList RequestCostList
if server.costTracker.testCostList != nil {
costList = server.costTracker.testCostList
} else {
costList = server.costTracker.makeCostList(server.costTracker.globalFactor())
}
send = send.add("flowControl/MRC", costList)
p.fcCosts = costList.decode(ProtocolLengths[uint(p.version)])
p.fcParams = server.defParams
// Add advertised checkpoint and register block height which
// client can verify the checkpoint validity.
if server.oracle != nil && server.oracle.isRunning() {
cp, height := server.oracle.stableCheckpoint()
if cp != nil {
send = send.add("checkpoint/value", cp)
send = send.add("checkpoint/registerHeight", height)
}
}
} else {
// Add some client-specific handshake fields
p.announceType = announceTypeSimple
if p.trusted {
p.announceType = announceTypeSigned
}
send = send.add("announceType", p.announceType)
}
recvList, err := p.sendReceiveHandshake(send)
if err != nil { if err != nil {
return err return err
} }
@ -640,47 +613,73 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
if p.rejectUpdate(size) { if p.rejectUpdate(size) {
return errResp(ErrRequestRejected, "") return errResp(ErrRequestRejected, "")
} }
// Check and compare the protocol version of remote peer
var remoteVersion uint64
if err := recv.get("protocolVersion", &remoteVersion); err != nil {
return err
}
if int(remoteVersion) != p.version {
return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", remoteVersion, p.version)
}
// Check and compare the network id of remote peer
var remoteNetwork uint64
if err := recv.get("networkId", &remoteNetwork); err != nil {
return err
}
if remoteNetwork != p.network {
return errResp(ErrNetworkIdMismatch, "%d (!= %d)", remoteNetwork, p.network)
}
// Check and compare the genesis of remote peer
var remoteGenesis common.Hash
if err := recv.get("genesisHash", &remoteGenesis); err != nil {
return err
}
if remoteGenesis != genesis {
return errResp(ErrGenesisBlockMismatch, "%x (!= %x)", remoteGenesis[:8], genesis[:8])
}
// Check forkID if the protocol version is beyond the les4
if p.version >= lpv4 {
var forkID forkid.ID
if err := recv.get("forkID", &forkID); err != nil {
return err
}
if err := forkFilter(forkID); err != nil {
return errResp(ErrForkIDRejected, "%v", err)
}
}
// Pass all checks, extract the remaning fields.
var remoteId *big.Int
if err := recv.get("headTd", &remoteId); err != nil {
return err
}
var remoteHead common.Hash
if err := recv.get("headHash", &remoteHead); err != nil {
return err
}
var remoteHeadNum uint64
if err := recv.get("headNum", &remoteHeadNum); err != nil {
return err
}
p.headInfo = &announceData{Hash: remoteHead, Number: remoteHeadNum, Td: remoteId}
if recvCallback != nil {
return recvCallback(recv)
}
return nil
}
var rGenesis, rHash common.Hash // handshakeWithServer executes the les protocol handshake with les server, negotiating
var rVersion, rNetwork, rNum uint64 // version number, network IDs, difficulties, head and genesis blocks.
var rTd *big.Int func (p *peer) handshakeWithServer(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter) error {
return p.handshake(td, head, headNum, genesis, forkID, forkFilter, func(lists *keyValueList) {
if err := recv.get("protocolVersion", &rVersion); err != nil { // Add some client-specific handshake fields
return err //
} // Enable signed announcement randomly even the server is not trusted.
if err := recv.get("networkId", &rNetwork); err != nil {
return err
}
if err := recv.get("headTd", &rTd); err != nil {
return err
}
if err := recv.get("headHash", &rHash); err != nil {
return err
}
if err := recv.get("headNum", &rNum); err != nil {
return err
}
if err := recv.get("genesisHash", &rGenesis); err != nil {
return err
}
if rGenesis != genesis {
return errResp(ErrGenesisBlockMismatch, "%x (!= %x)", rGenesis[:8], genesis[:8])
}
if rNetwork != p.network {
return errResp(ErrNetworkIdMismatch, "%d (!= %d)", rNetwork, p.network)
}
if int(rVersion) != p.version {
return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", rVersion, p.version)
}
if server != nil {
if recv.get("announceType", &p.announceType) != nil {
// set default announceType on server side
p.announceType = announceTypeSimple p.announceType = announceTypeSimple
if p.trusted || r.Intn(10) > 3 {
p.announceType = announceTypeSigned
} }
p.fcClient = flowcontrol.NewClientNode(server.fcManager, server.defParams) *lists = (*lists).add("announceType", p.announceType)
} else { }, func(recv keyValueMap) error {
if recv.get("serveChainSince", &p.chainSince) != nil { if recv.get("serveChainSince", &p.chainSince) != nil {
p.onlyAnnounce = true p.onlyAnnounce = true
} }
@ -696,11 +695,10 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
if recv.get("txRelay", nil) != nil { if recv.get("txRelay", nil) != nil {
p.onlyAnnounce = true p.onlyAnnounce = true
} }
if p.onlyAnnounce && !p.trusted { if p.onlyAnnounce && !p.trusted {
return errResp(ErrUselessPeer, "peer cannot serve requests") return errResp(ErrUselessPeer, "peer cannot serve requests")
} }
// Parse flow control handshake packet.
var sParams flowcontrol.ServerParams var sParams flowcontrol.ServerParams
if err := recv.get("flowControl/BL", &sParams.BufLimit); err != nil { if err := recv.get("flowControl/BL", &sParams.BufLimit); err != nil {
return err return err
@ -726,9 +724,59 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
} }
} }
} }
}
p.headInfo = &announceData{Td: rTd, Hash: rHash, Number: rNum}
return nil return nil
})
}
// handshakeWithClient executes the les protocol handshake with les client, negotiating
// version number, network IDs, difficulties, head and genesis blocks.
func (p *peer) handshakeWithClient(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter, server *LesServer) error {
return p.handshake(td, head, headNum, genesis, forkID, forkFilter, func(lists *keyValueList) {
// Add some information which services server can offer.
if !server.config.UltraLightOnlyAnnounce {
*lists = (*lists).add("serveHeaders", nil)
*lists = (*lists).add("serveChainSince", uint64(0))
*lists = (*lists).add("serveStateSince", uint64(0))
// If local ethereum node is running in archive mode, advertise ourselves we have
// all version state data. Otherwise only recent state is available.
stateRecent := uint64(core.TriesInMemory - 4)
if server.archiveMode {
stateRecent = 0
}
*lists = (*lists).add("serveRecentState", stateRecent)
*lists = (*lists).add("txRelay", nil)
}
*lists = (*lists).add("flowControl/BL", server.defParams.BufLimit)
*lists = (*lists).add("flowControl/MRR", server.defParams.MinRecharge)
var costList RequestCostList
if server.costTracker.testCostList != nil {
costList = server.costTracker.testCostList
} else {
costList = server.costTracker.makeCostList(server.costTracker.globalFactor())
}
*lists = (*lists).add("flowControl/MRC", costList)
p.fcCosts = costList.decode(ProtocolLengths[uint(p.version)])
p.fcParams = server.defParams
// Add advertised checkpoint and register block height which
// client can verify the checkpoint validity.
if server.oracle != nil && server.oracle.isRunning() {
cp, height := server.oracle.stableCheckpoint()
if cp != nil {
*lists = (*lists).add("checkpoint/value", cp)
*lists = (*lists).add("checkpoint/registerHeight", height)
}
}
}, func(recv keyValueMap) error {
if recv.get("announceType", &p.announceType) != nil {
// set default announceType on server side
p.announceType = announceTypeSimple
}
p.fcClient = flowcontrol.NewClientNode(server.fcManager, server.defParams)
return nil
})
} }
// updateFlowControl updates the flow control parameters belonging to the server // updateFlowControl updates the flow control parameters belonging to the server

View file

@ -17,50 +17,131 @@
package les package les
import ( import (
"crypto/rand"
"math/big" "math/big"
"net" "reflect"
"sort"
"testing" "testing"
"time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/forkid"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/les/flowcontrol"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/params"
) )
const protocolVersion = lpv2 type testServerPeerSub struct {
regCh chan *peer
var ( unregCh chan *peer
hash = common.HexToHash("deadbeef")
genesis = common.HexToHash("cafebabe")
headNum = uint64(1234)
td = big.NewInt(123)
)
func newNodeID(t *testing.T) *enode.Node {
key, err := crypto.GenerateKey()
if err != nil {
t.Fatal("generate key err:", err)
}
return enode.NewV4(&key.PublicKey, net.IP{}, 35000, 35000)
} }
// ulc connects to trusted peer and send announceType=announceTypeSigned func newTestServerPeerSub() *testServerPeerSub {
func TestPeerHandshakeSetAnnounceTypeToAnnounceTypeSignedForTrustedPeer(t *testing.T) { return &testServerPeerSub{
id := newNodeID(t).ID() regCh: make(chan *peer, 1),
unregCh: make(chan *peer, 1),
}
}
// peer to connect(on ulc side) func (t *testServerPeerSub) registerPeer(p *peer) { t.regCh <- p }
p := peer{ func (t *testServerPeerSub) unregisterPeer(p *peer) { t.unregCh <- p }
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
version: protocolVersion, func TestPeerSubscription(t *testing.T) {
trusted: true, peers := newPeerSet()
rw: &rwStub{ defer peers.Close()
WriteHook: func(recvList keyValueList) {
recv, _ := recvList.decode() checkIds := func(expect []string) {
given := peers.AllPeerIDs()
if len(given) == 0 && len(expect) == 0 {
return
}
sort.Strings(given)
sort.Strings(expect)
if !reflect.DeepEqual(given, expect) {
t.Fatalf("all peer ids mismatch, want %v, given %v", expect, given)
}
}
checkPeers := func(peerCh chan *peer) {
select {
case <-peerCh:
case <-time.NewTimer(100 * time.Millisecond).C:
t.Fatalf("timeout, no event received")
}
select {
case <-peerCh:
t.Fatalf("unexpected event received")
case <-time.NewTimer(10 * time.Millisecond).C:
}
}
checkIds([]string{})
sub := newTestServerPeerSub()
peers.notify(sub)
// Generate a random id and create the peer
var id enode.ID
rand.Read(id[:])
peer := newPeer(2, NetworkId, false, p2p.NewPeer(id, "name", nil), nil)
peers.Register(peer)
checkIds([]string{peer.id})
checkPeers(sub.regCh)
peers.Unregister(peer.id)
checkIds([]string{})
checkPeers(sub.unregCh)
}
func TestHandshakeLes2(t *testing.T) { testHandshake(t, lpv2) }
func TestHandshakeLes3(t *testing.T) { testHandshake(t, lpv3) }
func TestHandshakeLes4(t *testing.T) { testHandshake(t, lpv4) }
type fakeChain struct{}
func (f *fakeChain) Config() *params.ChainConfig { return params.MainnetChainConfig }
func (f *fakeChain) Genesis() *types.Block {
return core.DefaultGenesisBlock().ToBlock(rawdb.NewMemoryDatabase())
}
func (f *fakeChain) CurrentHeader() *types.Header { return &types.Header{Number: big.NewInt(10000000)} }
func testHandshake(t *testing.T, protocol int) {
// Create a message pipe to communicate through
app, net := p2p.MsgPipe()
// Generate a random id and create the peer
var id enode.ID
rand.Read(id[:])
peer1 := newPeer(protocol, NetworkId, false, p2p.NewPeer(id, "peer1", nil), net)
peer2 := newPeer(protocol, NetworkId, false, p2p.NewPeer(id, "peer2", nil), app)
var (
errCh1 = make(chan error, 1)
errCh2 = make(chan error, 1)
td = big.NewInt(100)
head = common.HexToHash("deadbeef")
headNum = uint64(10)
genesis = common.HexToHash("cafebabe")
)
chain1, chain2 := &fakeChain{}, &fakeChain{}
forkID1, forkID2 := forkid.NewID(chain1), forkid.NewID(chain2)
filter1, filter2 := forkid.NewFilter(chain1), forkid.NewFilter(chain2)
go func() {
// Exchange handshake with remote server peer
errCh1 <- peer1.handshake(td, head, headNum, genesis, forkID1, filter1, func(list *keyValueList) {
var announceType uint64 = announceTypeSigned
*list = (*list).add("announceType", announceType)
}, nil)
}()
go func() {
// Exchange handshake with remote client peer
errCh2 <- peer2.handshake(td, head, headNum, genesis, forkID2, filter2, nil, func(recv keyValueMap) error {
var reqType uint64 var reqType uint64
err := recv.get("announceType", &reqType) err := recv.get("announceType", &reqType)
if err != nil { if err != nil {
@ -69,234 +150,22 @@ func TestPeerHandshakeSetAnnounceTypeToAnnounceTypeSignedForTrustedPeer(t *testi
if reqType != announceTypeSigned { if reqType != announceTypeSigned {
t.Fatal("Expected announceTypeSigned") t.Fatal("Expected announceTypeSigned")
} }
},
ReadHook: func(l keyValueList) keyValueList {
l = l.add("serveHeaders", nil)
l = l.add("serveChainSince", uint64(0))
l = l.add("serveStateSince", uint64(0))
l = l.add("txRelay", nil)
l = l.add("flowControl/BL", uint64(0))
l = l.add("flowControl/MRR", uint64(0))
l = l.add("flowControl/MRC", testCostList(0))
return l
},
},
network: NetworkId,
}
err := p.Handshake(td, hash, headNum, genesis, nil)
if err != nil {
t.Fatalf("Handshake error: %s", err)
}
if p.announceType != announceTypeSigned {
t.Fatal("Incorrect announceType")
}
}
func TestPeerHandshakeAnnounceTypeSignedForTrustedPeersPeerNotInTrusted(t *testing.T) {
id := newNodeID(t).ID()
p := peer{
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
version: protocolVersion,
rw: &rwStub{
WriteHook: func(recvList keyValueList) {
// checking that ulc sends to peer allowedRequests=noRequests and announceType != announceTypeSigned
recv, _ := recvList.decode()
var reqType uint64
err := recv.get("announceType", &reqType)
if err != nil {
t.Fatal(err)
}
if reqType == announceTypeSigned {
t.Fatal("Expected not announceTypeSigned")
}
},
ReadHook: func(l keyValueList) keyValueList {
l = l.add("serveHeaders", nil)
l = l.add("serveChainSince", uint64(0))
l = l.add("serveStateSince", uint64(0))
l = l.add("txRelay", nil)
l = l.add("flowControl/BL", uint64(0))
l = l.add("flowControl/MRR", uint64(0))
l = l.add("flowControl/MRC", testCostList(0))
return l
},
},
network: NetworkId,
}
err := p.Handshake(td, hash, headNum, genesis, nil)
if err != nil {
t.Fatal(err)
}
if p.announceType == announceTypeSigned {
t.Fatal("Incorrect announceType")
}
}
func TestPeerHandshakeDefaultAllRequests(t *testing.T) {
id := newNodeID(t).ID()
s := generateLesServer()
p := peer{
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
version: protocolVersion,
rw: &rwStub{
ReadHook: func(l keyValueList) keyValueList {
l = l.add("announceType", uint64(announceTypeSigned))
l = l.add("allowedRequests", uint64(0))
return l
},
},
network: NetworkId,
}
err := p.Handshake(td, hash, headNum, genesis, s)
if err != nil {
t.Fatal(err)
}
if p.onlyAnnounce {
t.Fatal("Incorrect announceType")
}
}
func TestPeerHandshakeServerSendOnlyAnnounceRequestsHeaders(t *testing.T) {
id := newNodeID(t).ID()
s := generateLesServer()
s.config.UltraLightOnlyAnnounce = true
p := peer{
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
version: protocolVersion,
rw: &rwStub{
ReadHook: func(l keyValueList) keyValueList {
l = l.add("announceType", uint64(announceTypeSigned))
return l
},
WriteHook: func(l keyValueList) {
for _, v := range l {
if v.Key == "serveHeaders" ||
v.Key == "serveChainSince" ||
v.Key == "serveStateSince" ||
v.Key == "txRelay" {
t.Fatalf("%v exists", v.Key)
}
}
},
},
network: NetworkId,
}
err := p.Handshake(td, hash, headNum, genesis, s)
if err != nil {
t.Fatal(err)
}
}
func TestPeerHandshakeClientReceiveOnlyAnnounceRequestsHeaders(t *testing.T) {
id := newNodeID(t).ID()
p := peer{
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
version: protocolVersion,
rw: &rwStub{
ReadHook: func(l keyValueList) keyValueList {
l = l.add("flowControl/BL", uint64(0))
l = l.add("flowControl/MRR", uint64(0))
l = l.add("flowControl/MRC", RequestCostList{})
l = l.add("announceType", uint64(announceTypeSigned))
return l
},
},
network: NetworkId,
trusted: true,
}
err := p.Handshake(td, hash, headNum, genesis, nil)
if err != nil {
t.Fatal(err)
}
if !p.onlyAnnounce {
t.Fatal("onlyAnnounce must be true")
}
}
func TestPeerHandshakeClientReturnErrorOnUselessPeer(t *testing.T) {
id := newNodeID(t).ID()
p := peer{
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
version: protocolVersion,
rw: &rwStub{
ReadHook: func(l keyValueList) keyValueList {
l = l.add("flowControl/BL", uint64(0))
l = l.add("flowControl/MRR", uint64(0))
l = l.add("flowControl/MRC", RequestCostList{})
l = l.add("announceType", uint64(announceTypeSigned))
return l
},
},
network: NetworkId,
}
err := p.Handshake(td, hash, headNum, genesis, nil)
if err == nil {
t.FailNow()
}
}
func generateLesServer() *LesServer {
s := &LesServer{
lesCommons: lesCommons{
config: &eth.Config{UltraLightOnlyAnnounce: true},
},
defParams: flowcontrol.ServerParams{
BufLimit: uint64(300000000),
MinRecharge: uint64(50000),
},
fcManager: flowcontrol.NewClientManager(nil, &mclock.System{}),
}
s.costTracker, _ = newCostTracker(rawdb.NewMemoryDatabase(), s.config)
return s
}
type rwStub struct {
ReadHook func(l keyValueList) keyValueList
WriteHook func(l keyValueList)
}
func (s *rwStub) ReadMsg() (p2p.Msg, error) {
payload := keyValueList{}
payload = payload.add("protocolVersion", uint64(protocolVersion))
payload = payload.add("networkId", uint64(NetworkId))
payload = payload.add("headTd", td)
payload = payload.add("headHash", hash)
payload = payload.add("headNum", headNum)
payload = payload.add("genesisHash", genesis)
if s.ReadHook != nil {
payload = s.ReadHook(payload)
}
size, p, err := rlp.EncodeToReader(payload)
if err != nil {
return p2p.Msg{}, err
}
return p2p.Msg{
Size: uint32(size),
Payload: p,
}, nil
}
func (s *rwStub) WriteMsg(m p2p.Msg) error {
recvList := keyValueList{}
if err := m.Decode(&recvList); err != nil {
return err
}
if s.WriteHook != nil {
s.WriteHook(recvList)
}
return nil return nil
})
}()
for i := 0; i < 2; i++ {
select {
case err := <-errCh1:
if err != nil {
t.Fatalf("handshake failed, %v", err)
}
case err := <-errCh2:
if err != nil {
t.Fatalf("handshake failed, %v", err)
}
case <-time.NewTimer(500 * time.Millisecond).C:
t.Fatalf("timeout")
}
}
} }

View file

@ -33,17 +33,18 @@ import (
const ( const (
lpv2 = 2 lpv2 = 2
lpv3 = 3 lpv3 = 3
lpv4 = 4
) )
// Supported versions of the les protocol (first is primary) // Supported versions of the les protocol (first is primary)
var ( var (
ClientProtocolVersions = []uint{lpv2, lpv3} ClientProtocolVersions = []uint{lpv2, lpv3, lpv4}
ServerProtocolVersions = []uint{lpv2, lpv3} ServerProtocolVersions = []uint{lpv2, lpv3, lpv4}
AdvertiseProtocolVersions = []uint{lpv2} // clients are searching for the first advertised protocol in the list AdvertiseProtocolVersions = []uint{lpv2} // clients are searching for the first advertised protocol in the list
) )
// Number of implemented message corresponding to different protocol versions. // Number of implemented message corresponding to different protocol versions.
var ProtocolLengths = map[uint]uint64{lpv2: 22, lpv3: 24} var ProtocolLengths = map[uint]uint64{lpv2: 22, lpv3: 24, lpv4: 24}
const ( const (
NetworkId = 1 NetworkId = 1
@ -110,6 +111,7 @@ const (
ErrInvalidResponse ErrInvalidResponse
ErrTooManyTimeouts ErrTooManyTimeouts
ErrMissingKey ErrMissingKey
ErrForkIDRejected
) )
func (e errCode) String() string { func (e errCode) String() string {
@ -132,6 +134,7 @@ var errorToString = map[int]string{
ErrInvalidResponse: "Invalid response", ErrInvalidResponse: "Invalid response",
ErrTooManyTimeouts: "Too many request timeouts", ErrTooManyTimeouts: "Too many request timeouts",
ErrMissingKey: "Key missing from list", ErrMissingKey: "Key missing from list",
ErrForkIDRejected: "Forkid rejected",
} }
type announceBlock struct { type announceBlock struct {

View file

@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/forkid"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
@ -62,6 +63,7 @@ var (
// serverHandler is responsible for serving light client and process // serverHandler is responsible for serving light client and process
// all incoming light requests. // all incoming light requests.
type serverHandler struct { type serverHandler struct {
forkFilter forkid.Filter
blockchain *core.BlockChain blockchain *core.BlockChain
chainDb ethdb.Database chainDb ethdb.Database
txpool *core.TxPool txpool *core.TxPool
@ -77,6 +79,7 @@ type serverHandler struct {
func newServerHandler(server *LesServer, blockchain *core.BlockChain, chainDb ethdb.Database, txpool *core.TxPool, synced func() bool) *serverHandler { func newServerHandler(server *LesServer, blockchain *core.BlockChain, chainDb ethdb.Database, txpool *core.TxPool, synced func() bool) *serverHandler {
handler := &serverHandler{ handler := &serverHandler{
forkFilter: forkid.NewFilter(blockchain),
server: server, server: server,
blockchain: blockchain, blockchain: blockchain,
chainDb: chainDb, chainDb: chainDb,
@ -121,7 +124,7 @@ func (h *serverHandler) handle(p *peer) error {
number = head.Number.Uint64() number = head.Number.Uint64()
td = h.blockchain.GetTd(hash, number) td = h.blockchain.GetTd(hash, number)
) )
if err := p.Handshake(td, hash, number, h.blockchain.Genesis().Hash(), h.server); err != nil { if err := p.handshakeWithClient(td, hash, number, h.blockchain.Genesis().Hash(), forkid.NewID(h.blockchain), h.forkFilter, h.server); err != nil {
p.Log().Debug("Light Ethereum handshake failed", "err", err) p.Log().Debug("Light Ethereum handshake failed", "err", err)
return err return err
} }