les: handler separation

This commit is contained in:
rjl493456442 2019-05-26 18:58:26 +08:00
parent 962af9f50c
commit e5ff2cd3db
31 changed files with 2201 additions and 2479 deletions

View file

@ -170,8 +170,8 @@ type priorityClientInfo struct {
}
// newPriorityClientPool creates a new priority client pool
func newPriorityClientPool(freeClientCap uint64, ps *peerSet, child clientPool, metricsLogger, eventLogger *csvlogger.Logger) *priorityClientPool {
return &priorityClientPool{
func newPriorityClientPool(freeClientCap uint64, ps *peerSet, child clientPool, eventLogger *csvlogger.Logger, metricsLogger *csvlogger.Logger) *priorityClientPool {
pool := &priorityClientPool{
clients: make(map[enode.ID]priorityClientInfo),
freeClientCap: freeClientCap,
ps: ps,
@ -179,6 +179,8 @@ func newPriorityClientPool(freeClientCap uint64, ps *peerSet, child clientPool,
logger: eventLogger,
logTotalPriConn: metricsLogger.NewChannel("totalPriConn", 0),
}
ps.notify(pool)
return pool
}
// registerPeer is called when a new client is connected. If the client has no
@ -456,7 +458,7 @@ func (api *PrivateLightServerAPI) Benchmark(setups []map[string]interface{}, pas
return nil, ErrUnknownBenchmarkType
}
}
rs := api.server.protocolManager.runBenchmark(benchmarks, passCount, time.Millisecond*time.Duration(length))
rs := api.server.handler.runBenchmark(benchmarks, passCount, time.Millisecond*time.Duration(length))
result := make([]map[string]interface{}, len(setups))
for i, r := range rs {
res := make(map[string]interface{})
@ -476,14 +478,12 @@ func (api *PrivateLightServerAPI) Benchmark(setups []map[string]interface{}, pas
// PrivateLightAPI provides an API to access the LES light server or light client.
type PrivateLightAPI struct {
backend *lesCommons
reg *checkpointRegistrar
}
// NewPrivateLightAPI creates a new LES service API.
func NewPrivateLightAPI(backend *lesCommons, reg *checkpointRegistrar) *PrivateLightAPI {
func NewPrivateLightAPI(backend *lesCommons) *PrivateLightAPI {
return &PrivateLightAPI{
backend: backend,
reg: reg,
}
}
@ -513,7 +513,7 @@ func (api *PrivateLightAPI) LatestCheckpoint() ([4]string, error) {
// result[2], 32 bytes hex encoded latest section bloom trie root hash
func (api *PrivateLightAPI) GetCheckpoint(index uint64) ([3]string, error) {
var res [3]string
cp := api.backend.getLocalCheckpoint(index)
cp := api.backend.localCheckpoint(index)
if cp.Empty() {
return res, ErrNoCheckpoint
}
@ -523,8 +523,8 @@ func (api *PrivateLightAPI) GetCheckpoint(index uint64) ([3]string, error) {
// GetCheckpointContractAddress returns the contract contract address in hex format.
func (api *PrivateLightAPI) GetCheckpointContractAddress() (string, error) {
if api.reg == nil {
if api.backend.registrar == nil {
return "", ErrNotActivated
}
return api.reg.config.ContractAddr.Hex(), nil
return api.backend.registrar.config.ContractAddr.Hex(), nil
}

View file

@ -54,7 +54,7 @@ func (b *LesApiBackend) CurrentBlock() *types.Block {
}
func (b *LesApiBackend) SetHead(number uint64) {
b.eth.protocolManager.downloader.Cancel()
b.eth.handler.downloader.Cancel()
b.eth.blockchain.SetHead(number)
}

View file

@ -79,19 +79,16 @@ func TestCapacityAPI10(t *testing.T) {
// while connected and going back and forth between free and priority mode with
// the supplied API calls is also thoroughly tested.
func testCapacityAPI(t *testing.T, clientCount int) {
// Skip test if no data dir specified
if testServerDataDir == "" {
// Skip test if no data dir specified
return
}
for !testSim(t, 1, clientCount, []string{testServerDataDir}, nil, func(ctx context.Context, net *simulations.Network, servers []*simulations.Node, clients []*simulations.Node) bool {
if len(servers) != 1 {
t.Fatalf("Invalid number of servers: %d", len(servers))
}
server := servers[0]
clientRpcClients := make([]*rpc.Client, len(clients))
serverRpcClient, err := server.Client()
if err != nil {
t.Fatalf("Failed to obtain rpc client: %v", err)
@ -101,6 +98,7 @@ func testCapacityAPI(t *testing.T, clientCount int) {
minCap := getMinCap(ctx, t, serverRpcClient)
testCap := totalCap * 3 / 4
fmt.Printf("Server testCap: %d minCap: %d head number: %d head hash: %064x\n", testCap, minCap, headNum, headHash)
reqMinCap := uint64(float64(testCap) * minRelCap / (minRelCap + float64(len(clients)-1)))
if minCap > reqMinCap {
t.Fatalf("Minimum client capacity (%d) bigger than required minimum for this test (%d)", minCap, reqMinCap)
@ -109,13 +107,13 @@ func testCapacityAPI(t *testing.T, clientCount int) {
freeIdx := rand.Intn(len(clients))
freeCap := getFreeCap(ctx, t, serverRpcClient)
clientRpcClients := make([]*rpc.Client, len(clients))
for i, client := range clients {
var err error
clientRpcClients[i], err = client.Client()
if err != nil {
t.Fatalf("Failed to obtain rpc client: %v", err)
}
fmt.Println("connecting client", i)
if i != freeIdx {
setCapacity(ctx, t, serverRpcClient, client.ID(), testCap/uint64(len(clients)))
@ -142,10 +140,13 @@ func testCapacityAPI(t *testing.T, clientCount int) {
reqCount := make([]uint64, len(clientRpcClients))
// Send light request like crazy.
for i, c := range clientRpcClients {
wg.Add(1)
i, c := i, c
go func() {
defer wg.Done()
queue := make(chan struct{}, 100)
var count uint64
for {
@ -153,10 +154,8 @@ func testCapacityAPI(t *testing.T, clientCount int) {
case queue <- struct{}{}:
select {
case <-stop:
wg.Done()
return
case <-ctx.Done():
wg.Done()
return
default:
wg.Add(1)
@ -171,10 +170,8 @@ func testCapacityAPI(t *testing.T, clientCount int) {
}()
}
case <-stop:
wg.Done()
return
case <-ctx.Done():
wg.Done()
return
}
}
@ -315,12 +312,10 @@ func getHead(ctx context.Context, t *testing.T, client *rpc.Client) (uint64, com
}
func testRequest(ctx context.Context, t *testing.T, client *rpc.Client) bool {
//res := make(map[string]interface{})
var res string
var addr common.Address
rand.Read(addr[:])
c, _ := context.WithTimeout(ctx, time.Second*12)
// if err := client.CallContext(ctx, &res, "eth_getProof", addr, nil, "latest"); err != nil {
err := client.CallContext(c, &res, "eth_getBalance", addr, "latest")
if err != nil {
fmt.Println("request error:", err)
@ -417,7 +412,6 @@ func NewNetwork() (*simulations.Network, func(), error) {
adapterTeardown()
net.Shutdown()
}
return net, teardown, nil
}
@ -515,7 +509,6 @@ func newLesServerService(ctx *adapters.ServiceContext) (node.Service, error) {
if err != nil {
return nil, err
}
server, err := NewLesServer(ethereum, &config)
if err != nil {
return nil, err

View file

@ -39,7 +39,7 @@ import (
// requestBenchmark is an interface for different randomized request generators
type requestBenchmark interface {
// init initializes the generator for generating the given number of randomized requests
init(pm *ProtocolManager, count int) error
init(h *serverHandler, count int) error
// request initiates sending a single request to the given peer
request(peer *peer, index int) error
}
@ -52,10 +52,10 @@ type benchmarkBlockHeaders struct {
hashes []common.Hash
}
func (b *benchmarkBlockHeaders) init(pm *ProtocolManager, count int) error {
func (b *benchmarkBlockHeaders) init(h *serverHandler, count int) error {
d := int64(b.amount-1) * int64(b.skip+1)
b.offset = 0
b.randMax = pm.blockchain.CurrentHeader().Number.Int64() + 1 - d
b.randMax = h.blockchain.CurrentHeader().Number.Int64() + 1 - d
if b.randMax < 0 {
return fmt.Errorf("chain is too short")
}
@ -65,7 +65,7 @@ func (b *benchmarkBlockHeaders) init(pm *ProtocolManager, count int) error {
if b.byHash {
b.hashes = make([]common.Hash, count)
for i := range b.hashes {
b.hashes[i] = rawdb.ReadCanonicalHash(pm.chainDb, uint64(b.offset+rand.Int63n(b.randMax)))
b.hashes[i] = rawdb.ReadCanonicalHash(h.chainDb, uint64(b.offset+rand.Int63n(b.randMax)))
}
}
return nil
@ -85,11 +85,11 @@ type benchmarkBodiesOrReceipts struct {
hashes []common.Hash
}
func (b *benchmarkBodiesOrReceipts) init(pm *ProtocolManager, count int) error {
randMax := pm.blockchain.CurrentHeader().Number.Int64() + 1
func (b *benchmarkBodiesOrReceipts) init(h *serverHandler, count int) error {
randMax := h.blockchain.CurrentHeader().Number.Int64() + 1
b.hashes = make([]common.Hash, count)
for i := range b.hashes {
b.hashes[i] = rawdb.ReadCanonicalHash(pm.chainDb, uint64(rand.Int63n(randMax)))
b.hashes[i] = rawdb.ReadCanonicalHash(h.chainDb, uint64(rand.Int63n(randMax)))
}
return nil
}
@ -108,8 +108,8 @@ type benchmarkProofsOrCode struct {
headHash common.Hash
}
func (b *benchmarkProofsOrCode) init(pm *ProtocolManager, count int) error {
b.headHash = pm.blockchain.CurrentHeader().Hash()
func (b *benchmarkProofsOrCode) init(h *serverHandler, count int) error {
b.headHash = h.blockchain.CurrentHeader().Hash()
return nil
}
@ -130,11 +130,11 @@ type benchmarkHelperTrie struct {
sectionCount, headNum uint64
}
func (b *benchmarkHelperTrie) init(pm *ProtocolManager, count int) error {
func (b *benchmarkHelperTrie) init(h *serverHandler, count int) error {
if b.bloom {
b.sectionCount, b.headNum, _ = pm.server.bloomTrieIndexer.Sections()
b.sectionCount, b.headNum, _ = h.server.bloomTrieIndexer.Sections()
} else {
b.sectionCount, _, _ = pm.server.chtIndexer.Sections()
b.sectionCount, _, _ = h.server.chtIndexer.Sections()
b.headNum = b.sectionCount*params.CHTFrequency - 1
}
if b.sectionCount == 0 {
@ -170,7 +170,7 @@ type benchmarkTxSend struct {
txs types.Transactions
}
func (b *benchmarkTxSend) init(pm *ProtocolManager, count int) error {
func (b *benchmarkTxSend) init(h *serverHandler, count int) error {
key, _ := crypto.GenerateKey()
addr := crypto.PubkeyToAddress(key.PublicKey)
signer := types.NewEIP155Signer(big.NewInt(18))
@ -196,7 +196,7 @@ func (b *benchmarkTxSend) request(peer *peer, index int) error {
// benchmarkTxStatus implements requestBenchmark
type benchmarkTxStatus struct{}
func (b *benchmarkTxStatus) init(pm *ProtocolManager, count int) error {
func (b *benchmarkTxStatus) init(h *serverHandler, count int) error {
return nil
}
@ -217,7 +217,7 @@ type benchmarkSetup struct {
// runBenchmark runs a benchmark cycle for all benchmark types in the specified
// number of passes
func (pm *ProtocolManager) runBenchmark(benchmarks []requestBenchmark, passCount int, targetTime time.Duration) []*benchmarkSetup {
func (h *serverHandler) runBenchmark(benchmarks []requestBenchmark, passCount int, targetTime time.Duration) []*benchmarkSetup {
setup := make([]*benchmarkSetup, len(benchmarks))
for i, b := range benchmarks {
setup[i] = &benchmarkSetup{req: b}
@ -239,7 +239,7 @@ func (pm *ProtocolManager) runBenchmark(benchmarks []requestBenchmark, passCount
if next.totalTime > 0 {
count = int(uint64(next.totalCount) * uint64(targetTime) / uint64(next.totalTime))
}
if err := pm.measure(next, count); err != nil {
if err := h.measure(next, count); err != nil {
next.err = err
}
}
@ -275,14 +275,15 @@ func (m *meteredPipe) WriteMsg(msg p2p.Msg) error {
// measure runs a benchmark for a single type in a single pass, with the given
// number of requests
func (pm *ProtocolManager) measure(setup *benchmarkSetup, count int) error {
func (h *serverHandler) measure(setup *benchmarkSetup, count int) error {
clientPipe, serverPipe := p2p.MsgPipe()
clientMeteredPipe := &meteredPipe{rw: clientPipe}
serverMeteredPipe := &meteredPipe{rw: serverPipe}
var id enode.ID
rand.Read(id[:])
clientPeer := pm.newPeer(lpv2, NetworkId, p2p.NewPeer(id, "client", nil), clientMeteredPipe)
serverPeer := pm.newPeer(lpv2, NetworkId, p2p.NewPeer(id, "server", nil), serverMeteredPipe)
clientPeer := newPeer(lpv2, NetworkId, false, p2p.NewPeer(id, "client", nil), clientMeteredPipe)
serverPeer := newPeer(lpv2, NetworkId, false, p2p.NewPeer(id, "server", nil), serverMeteredPipe)
serverPeer.sendQueue = newExecQueue(count)
serverPeer.announceType = announceTypeNone
serverPeer.fcCosts = make(requestCostTable)
@ -291,10 +292,10 @@ func (pm *ProtocolManager) measure(setup *benchmarkSetup, count int) error {
serverPeer.fcCosts[code] = c
}
serverPeer.fcParams = flowcontrol.ServerParams{BufLimit: 1, MinRecharge: 1}
serverPeer.fcClient = flowcontrol.NewClientNode(pm.server.fcManager, serverPeer.fcParams)
serverPeer.fcClient = flowcontrol.NewClientNode(h.server.fcManager, serverPeer.fcParams)
defer serverPeer.fcClient.Disconnect()
if err := setup.req.init(pm, count); err != nil {
if err := setup.req.init(h, count); err != nil {
return err
}
@ -311,7 +312,7 @@ func (pm *ProtocolManager) measure(setup *benchmarkSetup, count int) error {
}()
go func() {
for i := 0; i < count; i++ {
if err := pm.handleMsg(serverPeer); err != nil {
if err := h.handleMsg(serverPeer); err != nil {
errCh <- err
return
}
@ -336,7 +337,7 @@ func (pm *ProtocolManager) measure(setup *benchmarkSetup, count int) error {
if err != nil {
return err
}
case <-pm.quitSync:
case <-h.closeCh:
clientPipe.Close()
serverPipe.Close()
return fmt.Errorf("Benchmark cancelled")

View file

@ -46,9 +46,10 @@ const (
func (eth *LightEthereum) startBloomHandlers(sectionSize uint64) {
for i := 0; i < bloomServiceThreads; i++ {
go func() {
defer eth.wg.Done()
for {
select {
case <-eth.shutdownChan:
case <-eth.closeCh:
return
case request := <-eth.bloomRequests:

View file

@ -19,8 +19,6 @@ package les
import (
"fmt"
"sync"
"time"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
@ -42,7 +40,7 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discv5"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
)
@ -50,33 +48,23 @@ import (
type LightEthereum struct {
lesCommons
odr *LesOdr
chainConfig *params.ChainConfig
// Channel for shutting down the service
shutdownChan chan bool
// Handlers
peers *peerSet
reqDist *requestDistributor
retriever *retrieveManager
odr *LesOdr
relay *lesTxRelay
handler *clientHandler
txPool *light.TxPool
blockchain *light.LightChain
serverPool *serverPool
reqDist *requestDistributor
retriever *retrieveManager
relay *lesTxRelay
bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests
bloomIndexer *core.ChainIndexer
ApiBackend *LesApiBackend
bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports
ApiBackend *LesApiBackend
eventMux *event.TypeMux
engine consensus.Engine
accountManager *accounts.Manager
networkId uint64
netRPCService *ethapi.PublicNetAPI
wg sync.WaitGroup
netRPCService *ethapi.PublicNetAPI
}
func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
@ -91,31 +79,24 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
log.Info("Initialised chain configuration", "config", chainConfig)
peers := newPeerSet()
quitSync := make(chan struct{})
leth := &LightEthereum{
lesCommons: lesCommons{
chainDb: chainDb,
config: config,
iConfig: light.DefaultClientIndexerConfig,
genesis: genesisHash,
config: config,
chainConfig: chainConfig,
iConfig: light.DefaultClientIndexerConfig,
chainDb: chainDb,
peers: peers,
closeCh: make(chan struct{}),
},
chainConfig: chainConfig,
eventMux: ctx.EventMux,
peers: peers,
reqDist: newRequestDistributor(peers, quitSync, &mclock.System{}),
reqDist: newRequestDistributor(peers, &mclock.System{}),
accountManager: ctx.AccountManager,
engine: eth.CreateConsensusEngine(ctx, chainConfig, &config.Ethash, nil, false, chainDb),
shutdownChan: make(chan bool),
networkId: config.NetworkId,
bloomRequests: make(chan chan *bloombits.Retrieval),
bloomIndexer: eth.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations),
serverPool: newServerPool(chainDb, config.ULC),
}
var trustedNodes []string
if leth.config.ULC != nil {
trustedNodes = leth.config.ULC.TrustedServers
}
leth.serverPool = newServerPool(chainDb, quitSync, &leth.wg, trustedNodes)
leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool)
leth.relay = newLesTxRelay(peers, leth.retriever)
@ -129,11 +110,20 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
if leth.blockchain, err = light.NewLightChain(leth.odr, leth.chainConfig, leth.engine); err != nil {
return nil, err
}
leth.chainReader = leth.blockchain
leth.txPool = light.NewTxPool(leth.chainConfig, leth.blockchain, leth.relay)
leth.registrar = newCheckpointRegistrar(chainConfig.CheckpointContract, leth.localCheckpoint)
// Note: AddChildIndexer starts the update process for the child
leth.bloomIndexer.AddChildIndexer(leth.bloomTrieIndexer)
leth.chtIndexer.Start(leth.blockchain)
leth.bloomIndexer.Start(leth.blockchain)
leth.handler = newClientHandler(config.ULC, leth)
if leth.handler.isULCEnabled() {
log.Warn("Ultra light client is enabled", "trustedNodes", len(leth.handler.ulc.trustedKeys), "minTrustedFraction", leth.handler.ulc.minTrustedFraction)
leth.blockchain.DisableCheckFreq()
}
// Rewind the chain in case of an incompatible config upgrade.
if compat, ok := genesisErr.(*params.ConfigCompatError); ok {
log.Warn("Rewinding chain to upgrade configuration", "err", compat)
@ -141,37 +131,16 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig)
}
leth.txPool = light.NewTxPool(leth.chainConfig, leth.blockchain, leth.relay)
leth.ApiBackend = &LesApiBackend{ctx.ExtRPCEnabled(), leth, nil}
gpoParams := config.GPO
if gpoParams.Default == nil {
gpoParams.Default = config.Miner.GasPrice
}
leth.ApiBackend.gpo = gasprice.NewOracle(leth.ApiBackend, gpoParams)
registrar := newCheckpointRegistrar(chainConfig.CheckpointContract, leth.getLocalCheckpoint)
if leth.protocolManager, err = NewProtocolManager(leth.chainConfig, light.DefaultClientIndexerConfig, config.ULC, true, config.NetworkId, leth.eventMux, leth.peers, leth.blockchain, nil, chainDb, leth.odr, leth.serverPool, registrar, quitSync, &leth.wg, nil); err != nil {
return nil, err
}
if leth.protocolManager.isULCEnabled() {
log.Warn("Ultra light client is enabled", "trustedNodes", len(leth.protocolManager.ulc.trustedKeys), "minTrustedFraction", leth.protocolManager.ulc.minTrustedFraction)
leth.blockchain.DisableCheckFreq()
}
return leth, nil
}
func lesTopic(genesisHash common.Hash, protocolVersion uint) discv5.Topic {
var name string
switch protocolVersion {
case lpv2:
name = "LES2"
default:
panic(nil)
}
return discv5.Topic(name + "@" + common.Bytes2Hex(genesisHash.Bytes()[0:8]))
}
type LightDummyAPI struct{}
// Etherbase is the address that mining rewards will be send to
@ -206,7 +175,7 @@ func (s *LightEthereum) APIs() []rpc.API {
}, {
Namespace: "eth",
Version: "1.0",
Service: downloader.NewPublicDownloaderAPI(s.protocolManager.downloader, s.eventMux),
Service: downloader.NewPublicDownloaderAPI(s.handler.downloader, s.eventMux),
Public: true,
}, {
Namespace: "eth",
@ -221,7 +190,7 @@ func (s *LightEthereum) APIs() []rpc.API {
}, {
Namespace: "les",
Version: "1.0",
Service: NewPrivateLightAPI(&s.lesCommons, s.protocolManager.reg),
Service: NewPrivateLightAPI(&s.lesCommons),
Public: false,
},
}...)
@ -235,54 +204,62 @@ func (s *LightEthereum) BlockChain() *light.LightChain { return s.blockchai
func (s *LightEthereum) TxPool() *light.TxPool { return s.txPool }
func (s *LightEthereum) Engine() consensus.Engine { return s.engine }
func (s *LightEthereum) LesVersion() int { return int(ClientProtocolVersions[0]) }
func (s *LightEthereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader }
func (s *LightEthereum) Downloader() *downloader.Downloader { return s.handler.downloader }
func (s *LightEthereum) EventMux() *event.TypeMux { return s.eventMux }
// Protocols implements node.Service, returning all the currently configured
// network protocols to start.
func (s *LightEthereum) Protocols() []p2p.Protocol {
return s.makeProtocols(ClientProtocolVersions)
return s.makeProtocols(ClientProtocolVersions, s.handler.runPeer, func(id enode.ID) interface{} {
if p := s.peers.Peer(fmt.Sprintf("%x", id.Bytes())); p != nil {
return p.Info()
}
return nil
})
}
// Start implements node.Service, starting all internal goroutines needed by the
// Ethereum protocol implementation.
// light ethereum protocol implementation.
func (s *LightEthereum) Start(srvr *p2p.Server) error {
log.Warn("Light client mode is an experimental feature")
// Start bloom request workers.
s.wg.Add(bloomServiceThreads)
s.startBloomHandlers(params.BloomBitsBlocksClient)
s.netRPCService = ethapi.NewPublicNetAPI(srvr, s.networkId)
s.netRPCService = ethapi.NewPublicNetAPI(srvr, s.config.NetworkId)
// clients are searching for the first advertised protocol in the list
protocolVersion := AdvertiseProtocolVersions[0]
s.serverPool.start(srvr, lesTopic(s.blockchain.Genesis().Hash(), protocolVersion))
s.protocolManager.Start(s.config.LightPeers)
return nil
}
// Stop implements node.Service, terminating all internal goroutines used by the
// Ethereum protocol.
func (s *LightEthereum) Stop() error {
close(s.closeCh)
s.peers.Close()
s.reqDist.close()
s.odr.Stop()
s.relay.Stop()
s.bloomIndexer.Close()
s.chtIndexer.Close()
s.blockchain.Stop()
s.protocolManager.Stop()
s.handler.stop()
s.txPool.Stop()
s.engine.Close()
s.eventMux.Stop()
time.Sleep(time.Millisecond * 200)
s.serverPool.stop()
s.chainDb.Close()
close(s.shutdownChan)
s.wg.Wait()
log.Info("Light ethereum stopped")
return nil
}
// SetClient sets the rpc client and binds the registrar contract.
func (s *LightEthereum) SetContractBackend(backend bind.ContractBackend) {
// Short circuit if registrar is nil
if s.protocolManager.reg == nil {
return
if s.registrar != nil {
s.registrar.start(backend)
}
s.protocolManager.reg.start(backend)
}

397
les/client_handler.go Normal file
View file

@ -0,0 +1,397 @@
// Copyright 2019 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 <http://www.gnu.org/licenses/>.
package les
import (
"math/big"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/params"
)
// clientHandler is responsible for receiving and process all incoming server responses.
type clientHandler struct {
ulc *ulc
fetcher *lightFetcher
downloader *downloader.Downloader
backend *LightEthereum
closeCh chan struct{}
wg sync.WaitGroup // WaitGroup used to track all connected peers.
}
func newClientHandler(ulcConfig *eth.ULCConfig, backend *LightEthereum) *clientHandler {
handler := &clientHandler{
backend: backend,
closeCh: make(chan struct{}),
}
if ulcConfig != nil {
handler.ulc = newULC(ulcConfig)
log.Info("Enable ultra light client mode")
}
var checkpoint uint64
if cht, ok := params.TrustedCheckpoints[backend.genesis]; ok {
checkpoint = (cht.SectionIndex+1)*params.CHTFrequency - 1
}
handler.fetcher = newLightFetcher(handler)
handler.downloader = downloader.New(checkpoint, backend.chainDb, nil, backend.eventMux, nil, backend.blockchain, handler.removePeer)
handler.backend.peers.notify((*downloaderPeerNotify)(handler))
return handler
}
func (h *clientHandler) stop() {
close(h.closeCh)
h.downloader.Terminate()
h.fetcher.close()
h.wg.Wait()
}
// runPeer is the p2p protocol run function for the given version.
func (h *clientHandler) runPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) error {
trusted := false
if h.isULCEnabled() {
trusted = h.ulc.isTrusted(p.ID())
}
peer := newPeer(int(version), h.backend.config.NetworkId, trusted, p, rw)
peer.poolEntry = h.backend.serverPool.connect(peer, peer.Node())
if peer.poolEntry == nil {
return p2p.DiscRequested
}
h.wg.Add(1)
defer h.wg.Done()
err := h.handle(peer)
h.backend.serverPool.disconnect(peer.poolEntry)
return err
}
func (h *clientHandler) handle(p *peer) error {
if h.backend.peers.Len() >= h.backend.config.LightPeers && !p.Peer.Info().Network.Trusted {
return p2p.DiscTooManyPeers
}
p.Log().Debug("Light Ethereum peer connected", "name", p.Name())
// Execute the LES handshake
var (
head = h.backend.blockchain.CurrentHeader()
hash = head.Hash()
number = head.Number.Uint64()
td = h.backend.blockchain.GetTd(hash, number)
)
if err := p.Handshake(td, hash, number, h.backend.blockchain.Genesis().Hash(), nil); err != nil {
p.Log().Debug("Light Ethereum handshake failed", "err", err)
return err
}
if rw, ok := p.rw.(*meteredMsgReadWriter); ok {
rw.Init(p.version)
}
// Register the peer locally
if err := h.backend.peers.Register(p); err != nil {
p.Log().Error("Light Ethereum peer registration failed", "err", err)
return err
}
defer h.backend.peers.Unregister(p.id)
h.fetcher.announce(p, p.headInfo)
// pool entry can be nil during the unit test.
if p.poolEntry != nil {
h.backend.serverPool.registered(p.poolEntry)
}
// Spawn a main loop to handle all incoming messages.
for {
if err := h.handleMsg(p); err != nil {
p.Log().Debug("Light Ethereum message handling failed", "err", err)
p.fcServer.DumpLogs()
return err
}
}
}
// handleMsg is invoked whenever an inbound message is received from a remote
// peer. The remote connection is torn down upon returning any error.
func (h *clientHandler) handleMsg(p *peer) error {
// Read the next message from the remote peer, and ensure it's fully consumed
msg, err := p.rw.ReadMsg()
if err != nil {
return err
}
p.Log().Trace("Light Ethereum message arrived", "code", msg.Code, "bytes", msg.Size)
if msg.Size > ProtocolMaxMsgSize {
return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, ProtocolMaxMsgSize)
}
defer msg.Discard()
var deliverMsg *Msg
// Handle the message depending on its contents
switch msg.Code {
case AnnounceMsg:
p.Log().Trace("Received announce message")
var req announceData
if err := msg.Decode(&req); err != nil {
return errResp(ErrDecode, "%v: %v", msg, err)
}
update, size := req.Update.decode()
if p.rejectUpdate(size) {
return errResp(ErrRequestRejected, "")
}
p.updateFlowControl(update)
if req.Hash != (common.Hash{}) {
if p.announceType == announceTypeNone {
return errResp(ErrUnexpectedResponse, "")
}
if p.announceType == announceTypeSigned {
if err := req.checkSignature(p.ID(), update); err != nil {
p.Log().Trace("Invalid announcement signature", "err", err)
return err
}
p.Log().Trace("Valid announcement signature")
}
p.Log().Trace("Announce message content", "number", req.Number, "hash", req.Hash, "td", req.Td, "reorg", req.ReorgDepth)
h.fetcher.announce(p, &req)
}
case BlockHeadersMsg:
p.Log().Trace("Received block header response message")
var resp struct {
ReqID, BV uint64
Headers []*types.Header
}
if err := msg.Decode(&resp); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
p.fcServer.ReceivedReply(resp.ReqID, resp.BV)
if h.fetcher.requestedID(resp.ReqID) {
h.fetcher.deliverHeaders(p, resp.ReqID, resp.Headers)
} else {
if err := h.downloader.DeliverHeaders(p.id, resp.Headers); err != nil {
log.Debug("Failed to deliver headers", "err", err)
}
}
case BlockBodiesMsg:
p.Log().Trace("Received block bodies response")
var resp struct {
ReqID, BV uint64
Data []*types.Body
}
if err := msg.Decode(&resp); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
p.fcServer.ReceivedReply(resp.ReqID, resp.BV)
deliverMsg = &Msg{
MsgType: MsgBlockBodies,
ReqID: resp.ReqID,
Obj: resp.Data,
}
case CodeMsg:
p.Log().Trace("Received code response")
var resp struct {
ReqID, BV uint64
Data [][]byte
}
if err := msg.Decode(&resp); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
p.fcServer.ReceivedReply(resp.ReqID, resp.BV)
deliverMsg = &Msg{
MsgType: MsgCode,
ReqID: resp.ReqID,
Obj: resp.Data,
}
case ReceiptsMsg:
p.Log().Trace("Received receipts response")
var resp struct {
ReqID, BV uint64
Receipts []types.Receipts
}
if err := msg.Decode(&resp); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
p.fcServer.ReceivedReply(resp.ReqID, resp.BV)
deliverMsg = &Msg{
MsgType: MsgReceipts,
ReqID: resp.ReqID,
Obj: resp.Receipts,
}
case ProofsV2Msg:
p.Log().Trace("Received les/2 proofs response")
var resp struct {
ReqID, BV uint64
Data light.NodeList
}
if err := msg.Decode(&resp); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
p.fcServer.ReceivedReply(resp.ReqID, resp.BV)
deliverMsg = &Msg{
MsgType: MsgProofsV2,
ReqID: resp.ReqID,
Obj: resp.Data,
}
case HelperTrieProofsMsg:
p.Log().Trace("Received helper trie proof response")
var resp struct {
ReqID, BV uint64
Data HelperTrieResps
}
if err := msg.Decode(&resp); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
p.fcServer.ReceivedReply(resp.ReqID, resp.BV)
deliverMsg = &Msg{
MsgType: MsgHelperTrieProofs,
ReqID: resp.ReqID,
Obj: resp.Data,
}
case TxStatusMsg:
p.Log().Trace("Received tx status response")
var resp struct {
ReqID, BV uint64
Status []light.TxStatus
}
if err := msg.Decode(&resp); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
p.fcServer.ReceivedReply(resp.ReqID, resp.BV)
deliverMsg = &Msg{
MsgType: MsgTxStatus,
ReqID: resp.ReqID,
Obj: resp.Status,
}
case StopMsg:
p.freezeServer(true)
h.backend.retriever.frozen(p)
p.Log().Warn("Service stopped")
case ResumeMsg:
var bv uint64
if err := msg.Decode(&bv); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
p.fcServer.ResumeFreeze(bv)
p.freezeServer(false)
p.Log().Warn("Service resumed")
default:
p.Log().Trace("Received invalid message", "code", msg.Code)
return errResp(ErrInvalidMsgCode, "%v", msg.Code)
}
// Deliver the received response to retriever.
if deliverMsg != nil {
if err := h.backend.retriever.deliver(p, deliverMsg); err != nil {
p.responseErrors++
if p.responseErrors > maxResponseErrors {
return err
}
}
}
return nil
}
func (h *clientHandler) removePeer(id string) {
h.backend.peers.Unregister(id)
}
// isULCEnabled returns an indicator whether we are running light
// client in ultra light client mode.
func (h *clientHandler) isULCEnabled() bool {
if h.ulc == nil || len(h.ulc.trustedKeys) == 0 {
return false
}
return true
}
type peerConnection struct {
handler *clientHandler
peer *peer
}
func (pc *peerConnection) Head() (common.Hash, *big.Int) {
return pc.peer.HeadAndTd()
}
func (pc *peerConnection) RequestHeadersByHash(origin common.Hash, amount int, skip int, reverse bool) error {
rq := &distReq{
getCost: func(dp distPeer) uint64 {
peer := dp.(*peer)
return peer.GetRequestCost(GetBlockHeadersMsg, amount)
},
canSend: func(dp distPeer) bool {
return dp.(*peer) == pc.peer
},
request: func(dp distPeer) func() {
reqID := genReqID()
peer := dp.(*peer)
cost := peer.GetRequestCost(GetBlockHeadersMsg, amount)
peer.fcServer.QueuedRequest(reqID, cost)
return func() { peer.RequestHeadersByHash(reqID, cost, origin, amount, skip, reverse) }
},
}
_, ok := <-pc.handler.backend.reqDist.queue(rq)
if !ok {
return light.ErrNoPeers
}
return nil
}
func (pc *peerConnection) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool) error {
rq := &distReq{
getCost: func(dp distPeer) uint64 {
peer := dp.(*peer)
return peer.GetRequestCost(GetBlockHeadersMsg, amount)
},
canSend: func(dp distPeer) bool {
return dp.(*peer) == pc.peer
},
request: func(dp distPeer) func() {
reqID := genReqID()
peer := dp.(*peer)
cost := peer.GetRequestCost(GetBlockHeadersMsg, amount)
peer.fcServer.QueuedRequest(reqID, cost)
return func() { peer.RequestHeadersByNumber(reqID, cost, origin, amount, skip, reverse) }
},
}
_, ok := <-pc.handler.backend.reqDist.queue(rq)
if !ok {
return light.ErrNoPeers
}
return nil
}
// downloaderPeerNotify implements peerSetNotify
type downloaderPeerNotify clientHandler
func (d *downloaderPeerNotify) registerPeer(p *peer) {
h := (*clientHandler)(d)
pc := &peerConnection{
handler: h,
peer: p,
}
h.downloader.RegisterLightPeer(p.id, ethVersion, pc)
}
func (d *downloaderPeerNotify) unregisterPeer(p *peer) {
h := (*clientHandler)(d)
h.downloader.UnregisterPeer(p.id)
}

View file

@ -20,23 +20,54 @@ import (
"fmt"
"math/big"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discv5"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/params"
)
func errResp(code errCode, format string, v ...interface{}) error {
return fmt.Errorf("%v - %v", code, fmt.Sprintf(format, v...))
}
func lesTopic(genesisHash common.Hash, protocolVersion uint) discv5.Topic {
var name string
switch protocolVersion {
case lpv2:
name = "LES2"
default:
panic(nil)
}
return discv5.Topic(name + "@" + common.Bytes2Hex(genesisHash.Bytes()[0:8]))
}
type chainReader interface {
CurrentHeader() *types.Header
}
// lesCommons contains fields needed by both server and client.
type lesCommons struct {
genesis common.Hash
config *eth.Config
chainConfig *params.ChainConfig
iConfig *light.IndexerConfig
chainDb ethdb.Database
protocolManager *ProtocolManager
peers *peerSet
chainReader chainReader
chtIndexer, bloomTrieIndexer *core.ChainIndexer
registrar *checkpointRegistrar
closeCh chan struct{}
wg sync.WaitGroup
}
// NodeInfo represents a short summary of the Ethereum sub-protocol metadata
@ -51,7 +82,7 @@ type NodeInfo struct {
}
// makeProtocols creates protocol descriptors for the given LES versions.
func (c *lesCommons) makeProtocols(versions []uint) []p2p.Protocol {
func (c *lesCommons) makeProtocols(versions []uint, runPeer func(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) error, peerInfo func(id enode.ID) interface{}) []p2p.Protocol {
protos := make([]p2p.Protocol, len(versions))
for i, version := range versions {
version := version
@ -60,15 +91,10 @@ func (c *lesCommons) makeProtocols(versions []uint) []p2p.Protocol {
Version: version,
Length: ProtocolLengths[version],
NodeInfo: c.nodeInfo,
Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
return c.protocolManager.runPeer(version, p, rw)
},
PeerInfo: func(id enode.ID) interface{} {
if p := c.protocolManager.peers.Peer(fmt.Sprintf("%x", id.Bytes())); p != nil {
return p.Info()
}
return nil
Run: func(peer *p2p.Peer, rw p2p.MsgReadWriter) error {
return runPeer(version, peer, rw)
},
PeerInfo: peerInfo,
}
}
return protos
@ -76,15 +102,14 @@ func (c *lesCommons) makeProtocols(versions []uint) []p2p.Protocol {
// nodeInfo retrieves some protocol metadata about the running host node.
func (c *lesCommons) nodeInfo() interface{} {
chain := c.protocolManager.blockchain
head := chain.CurrentHeader()
head := c.chainReader.CurrentHeader()
hash := head.Hash()
return &NodeInfo{
Network: c.config.NetworkId,
Difficulty: chain.GetTd(hash, head.Number.Uint64()),
Genesis: chain.Genesis().Hash(),
Config: chain.Config(),
Head: chain.CurrentHeader().Hash(),
Difficulty: rawdb.ReadTd(c.chainDb, hash, head.Number.Uint64()),
Genesis: c.genesis,
Config: c.chainConfig,
Head: hash,
CHT: c.latestLocalCheckpoint(),
}
}
@ -103,15 +128,15 @@ func (c *lesCommons) latestLocalCheckpoint() params.TrustedCheckpoint {
// No checkpoint information can be provided.
return params.TrustedCheckpoint{}
}
return c.getLocalCheckpoint(sections - 1)
return c.localCheckpoint(sections - 1)
}
// getLocalCheckpoint returns a set of post-processed trie roots (CHT and BloomTrie)
// localCheckpoint returns a set of post-processed trie roots (CHT and BloomTrie)
// associated with the appropriate head hash by specific section index.
//
// The returned checkpoint is only the checkpoint generated by the local indexers,
// not the stable checkpoint registered in the registrar contract.
func (c *lesCommons) getLocalCheckpoint(index uint64) params.TrustedCheckpoint {
func (c *lesCommons) localCheckpoint(index uint64) params.TrustedCheckpoint {
sectionHead := c.chtIndexer.SectionHead(index)
return params.TrustedCheckpoint{
SectionIndex: index,

View file

@ -84,6 +84,7 @@ var (
const (
maxCostFactor = 2 // ratio of maximum and average cost estimates
bufLimitRatio = 6000 // fixed bufLimit/MRR ratio
gfUsageThreshold = 0.5
gfUsageTC = time.Second
gfRaiseTC = time.Second * 200
@ -115,6 +116,10 @@ type costTracker struct {
stats map[uint64][]uint64
logger *csvlogger.Logger
logRecentTime, logRecentAvg, logTotalRecharge, logRelCost *csvlogger.Channel
// TestHooks
disableRealCost bool // Disable real cost evaluation for testing purpose.
costListHook func() RequestCostList // Return customized cost table for testing purpose.
}
// newCostTracker creates a cost tracker and loads the cost factor statistics from the database.

View file

@ -28,14 +28,17 @@ import (
// suitable peers, obeying flow control rules and prioritizing them in creation
// order (even when a resend is necessary).
type requestDistributor struct {
clock mclock.Clock
reqQueue *list.List
lastReqOrder uint64
peers map[distPeer]struct{}
peerLock sync.RWMutex
stopChn, loopChn chan struct{}
loopNextSent bool
lock sync.Mutex
clock mclock.Clock
reqQueue *list.List
lastReqOrder uint64
peers map[distPeer]struct{}
peerLock sync.RWMutex
loopChn chan struct{}
loopNextSent bool
lock sync.Mutex
closeCh chan struct{}
wg sync.WaitGroup
}
// distPeer is an LES server peer interface for the request distributor.
@ -69,17 +72,18 @@ type distReq struct {
}
// newRequestDistributor creates a new request distributor
func newRequestDistributor(peers *peerSet, stopChn chan struct{}, clock mclock.Clock) *requestDistributor {
func newRequestDistributor(peers *peerSet, clock mclock.Clock) *requestDistributor {
d := &requestDistributor{
clock: clock,
reqQueue: list.New(),
loopChn: make(chan struct{}, 2),
stopChn: stopChn,
closeCh: make(chan struct{}),
peers: make(map[distPeer]struct{}),
}
if peers != nil {
peers.notify(d)
}
d.wg.Add(1)
go d.loop()
return d
}
@ -115,9 +119,10 @@ const waitForPeers = time.Second * 3
// main event loop
func (d *requestDistributor) loop() {
defer d.wg.Done()
for {
select {
case <-d.stopChn:
case <-d.closeCh:
d.lock.Lock()
elem := d.reqQueue.Front()
for elem != nil {
@ -294,3 +299,8 @@ func (d *requestDistributor) remove(r *distReq) {
r.element = nil
}
}
func (d *requestDistributor) close() {
close(d.closeCh)
d.wg.Wait()
}

View file

@ -121,7 +121,7 @@ func testRequestDistributor(t *testing.T, resend bool) {
stop := make(chan struct{})
defer close(stop)
dist := newRequestDistributor(nil, stop, &mclock.System{})
dist := newRequestDistributor(nil, &mclock.System{})
var peers [testDistPeerCount]*testDistPeer
for i := range peers {
peers[i] = &testDistPeer{}

View file

@ -40,9 +40,8 @@ const (
// ODR system to ensure that we only request data related to a certain block from peers who have already processed
// and announced that block.
type lightFetcher struct {
pm *ProtocolManager
odr *LesOdr
chain lightChain
handler *clientHandler
chain *light.LightChain
lock sync.Mutex // lock protects access to the fetcher's internal state variables except sent requests
maxConfirmedTd *big.Int
@ -58,13 +57,9 @@ type lightFetcher struct {
requestTriggered bool
requestTrigger chan struct{}
lastTrustedHeader *types.Header
}
// lightChain extends the BlockChain interface by locking.
type lightChain interface {
BlockChain
LockChain()
UnlockChain()
closeCh chan struct{}
wg sync.WaitGroup
}
// fetcherPeerInfo holds fetcher-specific information about each active peer
@ -114,32 +109,36 @@ type fetchResponse struct {
}
// newLightFetcher creates a new light fetcher
func newLightFetcher(pm *ProtocolManager) *lightFetcher {
func newLightFetcher(h *clientHandler) *lightFetcher {
f := &lightFetcher{
pm: pm,
chain: pm.blockchain.(*light.LightChain),
odr: pm.odr,
handler: h,
chain: h.backend.blockchain,
peers: make(map[*peer]*fetcherPeerInfo),
deliverChn: make(chan fetchResponse, 100),
requested: make(map[uint64]fetchRequest),
timeoutChn: make(chan uint64),
requestTrigger: make(chan struct{}, 1),
syncDone: make(chan *peer),
closeCh: make(chan struct{}),
maxConfirmedTd: big.NewInt(0),
}
pm.peers.notify(f)
h.backend.peers.notify(f)
f.pm.wg.Add(1)
f.wg.Add(1)
go f.syncLoop()
return f
}
func (f *lightFetcher) close() {
close(f.closeCh)
}
// syncLoop is the main event loop of the light fetcher
func (f *lightFetcher) syncLoop() {
defer f.pm.wg.Done()
defer f.wg.Done()
for {
select {
case <-f.pm.quitSync:
case <-f.closeCh:
return
// request loop keeps running until no further requests are necessary or possible
case <-f.requestTrigger:
@ -156,7 +155,7 @@ func (f *lightFetcher) syncLoop() {
f.lock.Unlock()
if rq != nil {
if _, ok := <-f.pm.reqDist.queue(rq); ok {
if _, ok := <-f.handler.backend.reqDist.queue(rq); ok {
if syncing {
f.lock.Lock()
f.syncing = true
@ -187,9 +186,9 @@ func (f *lightFetcher) syncLoop() {
}
f.reqMu.Unlock()
if ok {
f.pm.serverPool.adjustResponseTime(req.peer.poolEntry, time.Duration(mclock.Now()-req.sent), true)
f.handler.backend.serverPool.adjustResponseTime(req.peer.poolEntry, time.Duration(mclock.Now()-req.sent), true)
req.peer.Log().Debug("Fetching data timed out hard")
go f.pm.removePeer(req.peer.id)
go f.handler.removePeer(req.peer.id)
}
case resp := <-f.deliverChn:
f.reqMu.Lock()
@ -202,12 +201,12 @@ func (f *lightFetcher) syncLoop() {
}
f.reqMu.Unlock()
if ok {
f.pm.serverPool.adjustResponseTime(req.peer.poolEntry, time.Duration(mclock.Now()-req.sent), req.timeout)
f.handler.backend.serverPool.adjustResponseTime(req.peer.poolEntry, time.Duration(mclock.Now()-req.sent), req.timeout)
}
f.lock.Lock()
if !ok || !(f.syncing || f.processResponse(req, resp)) {
resp.peer.Log().Debug("Failed processing response")
go f.pm.removePeer(resp.peer.id)
go f.handler.removePeer(resp.peer.id)
}
f.lock.Unlock()
case p := <-f.syncDone:
@ -264,7 +263,7 @@ func (f *lightFetcher) announce(p *peer, head *announceData) {
if fp.lastAnnounced != nil && head.Td.Cmp(fp.lastAnnounced.td) <= 0 {
// announced tds should be strictly monotonic
p.Log().Debug("Received non-monotonic td", "current", head.Td, "previous", fp.lastAnnounced.td)
go f.pm.removePeer(p.id)
go f.handler.removePeer(p.id)
return
}
@ -297,7 +296,7 @@ func (f *lightFetcher) announce(p *peer, head *announceData) {
// if one of root's children is canonical, keep it, delete other branches and root itself
var newRoot *fetcherTreeNode
for i, nn := range fp.root.children {
if rawdb.ReadCanonicalHash(f.pm.chainDb, nn.number) == nn.hash {
if rawdb.ReadCanonicalHash(f.handler.backend.chainDb, nn.number) == nn.hash {
fp.root.children = append(fp.root.children[:i], fp.root.children[i+1:]...)
nn.parent = nil
newRoot = nn
@ -390,7 +389,7 @@ func (f *lightFetcher) peerHasBlock(p *peer, hash common.Hash, number uint64, ha
//
// when syncing, just check if it is part of the known chain, there is nothing better we
// can do since we do not know the most recent block hash yet
return rawdb.ReadCanonicalHash(f.pm.chainDb, fp.root.number) == fp.root.hash && rawdb.ReadCanonicalHash(f.pm.chainDb, number) == hash
return rawdb.ReadCanonicalHash(f.handler.backend.chainDb, fp.root.number) == fp.root.hash && rawdb.ReadCanonicalHash(f.handler.backend.chainDb, number) == hash
}
// requestAmount calculates the amount of headers to be downloaded starting
@ -453,10 +452,9 @@ func (f *lightFetcher) findBestRequest() (bestHash common.Hash, bestAmount uint6
if f.checkKnownNode(p, n) || n.requested {
continue
}
//if ulc mode is disabled, isTrustedHash returns true
// if ulc mode is disabled, isTrustedHash returns true
amount := f.requestAmount(p, n)
if (bestTd == nil || n.td.Cmp(bestTd) > 0 || amount < bestAmount) && (f.isTrustedHash(hash) || f.maxConfirmedTd.Int64() == 0) {
if (bestTd == nil || n.td.Cmp(bestTd) > 0 || amount < bestAmount) && f.isTrustedHash(hash) {
bestHash = hash
bestTd = n.td
bestAmount = amount
@ -469,23 +467,20 @@ func (f *lightFetcher) findBestRequest() (bestHash common.Hash, bestAmount uint6
// isTrustedHash checks if the block can be trusted by the minimum trusted fraction.
func (f *lightFetcher) isTrustedHash(hash common.Hash) bool {
if !f.pm.isULCEnabled() {
if !f.handler.isULCEnabled() {
return true
}
var numAgreed int
for p, fp := range f.peers {
if !p.isTrusted {
if !p.trusted {
continue
}
if _, ok := fp.nodeByHash[hash]; !ok {
continue
}
numAgreed++
}
return 100*numAgreed/len(f.pm.ulc.trustedKeys) >= f.pm.ulc.minTrustedFraction
return 100*numAgreed/len(f.handler.ulc.trustedKeys) >= f.handler.ulc.minTrustedFraction
}
func (f *lightFetcher) newFetcherDistReqForSync(bestHash common.Hash) *distReq {
@ -498,7 +493,7 @@ func (f *lightFetcher) newFetcherDistReqForSync(bestHash common.Hash) *distReq {
f.lock.Lock()
defer f.lock.Unlock()
if p.isOnlyAnnounce {
if p.announceOnly {
return false
}
@ -506,14 +501,14 @@ func (f *lightFetcher) newFetcherDistReqForSync(bestHash common.Hash) *distReq {
return fp != nil && fp.nodeByHash[bestHash] != nil
},
request: func(dp distPeer) func() {
if f.pm.isULCEnabled() {
if f.handler.isULCEnabled() {
//keep last trusted header before sync
f.setLastTrustedHeader(f.chain.CurrentHeader())
}
go func() {
p := dp.(*peer)
p.Log().Debug("Synchronisation started")
f.pm.synchronise(p)
f.handler.synchronise(p)
f.syncDone <- p
}()
return nil
@ -533,7 +528,7 @@ func (f *lightFetcher) newFetcherDistReq(bestHash common.Hash, reqID uint64, bes
f.lock.Lock()
defer f.lock.Unlock()
if p.isOnlyAnnounce {
if p.announceOnly {
return false
}
@ -614,7 +609,7 @@ func (f *lightFetcher) newHeaders(headers []*types.Header, tds []*big.Int) {
for p, fp := range f.peers {
if !f.checkAnnouncedHeaders(fp, headers, tds) {
p.Log().Debug("Inconsistent announcement")
go f.pm.removePeer(p.id)
go f.handler.removePeer(p.id)
}
if fp.confirmedTd != nil && (maxTd == nil || maxTd.Cmp(fp.confirmedTd) > 0) {
maxTd = fp.confirmedTd
@ -713,7 +708,7 @@ func (f *lightFetcher) checkSyncedHeaders(p *peer) {
var td *big.Int
var h *types.Header
if f.pm.isULCEnabled() {
if f.handler.isULCEnabled() {
var unapprovedHashes []common.Hash
// Overwrite last announced for ULC mode
h, unapprovedHashes = f.lastTrustedTreeNode(p)
@ -734,7 +729,7 @@ func (f *lightFetcher) checkSyncedHeaders(p *peer) {
// Now n is the latest downloaded/approved header after syncing
if n == nil {
p.Log().Debug("Synchronisation failed")
go f.pm.removePeer(p.id)
go f.handler.removePeer(p.id)
return
}
header := f.chain.GetHeader(n.hash, n.number)
@ -754,7 +749,7 @@ func (f *lightFetcher) lastTrustedTreeNode(p *peer) (*types.Header, []common.Has
if canonical.Number.Uint64() > f.lastTrustedHeader.Number.Uint64() {
canonical = f.chain.GetHeaderByNumber(f.lastTrustedHeader.Number.Uint64())
}
commonAncestor := rawdb.FindCommonAncestor(f.pm.chainDb, canonical, f.lastTrustedHeader)
commonAncestor := rawdb.FindCommonAncestor(f.handler.backend.chainDb, canonical, f.lastTrustedHeader)
if commonAncestor == nil {
log.Error("Common ancestor of last trusted header and canonical header is nil", "canonical hash", canonical.Hash(), "trusted hash", f.lastTrustedHeader.Hash())
return current, unapprovedHashes
@ -800,7 +795,7 @@ func (f *lightFetcher) checkKnownNode(p *peer, n *fetcherTreeNode) bool {
}
if !f.checkAnnouncedHeaders(fp, []*types.Header{header}, []*big.Int{td}) {
p.Log().Debug("Inconsistent announcement")
go f.pm.removePeer(p.id)
go f.handler.removePeer(p.id)
}
if fp.confirmedTd != nil {
f.updateMaxConfirmedTd(fp.confirmedTd)
@ -893,12 +888,12 @@ func (f *lightFetcher) checkUpdateStats(p *peer, newEntry *updateStatsEntry) {
fp.firstUpdateStats = newEntry
}
for fp.firstUpdateStats != nil && fp.firstUpdateStats.time <= now-mclock.AbsTime(blockDelayTimeout) {
f.pm.serverPool.adjustBlockDelay(p.poolEntry, blockDelayTimeout)
f.handler.backend.serverPool.adjustBlockDelay(p.poolEntry, blockDelayTimeout)
fp.firstUpdateStats = fp.firstUpdateStats.next
}
if fp.confirmedTd != nil {
for fp.firstUpdateStats != nil && fp.firstUpdateStats.td.Cmp(fp.confirmedTd) <= 0 {
f.pm.serverPool.adjustBlockDelay(p.poolEntry, time.Duration(now-fp.firstUpdateStats.time))
f.handler.backend.serverPool.adjustBlockDelay(p.poolEntry, time.Duration(now-fp.firstUpdateStats.time))
fp.firstUpdateStats = fp.firstUpdateStats.next
}
}

View file

@ -1,152 +0,0 @@
package les
import (
"math/big"
"testing"
"net"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode"
)
func TestFetcherULCPeerSelector(t *testing.T) {
id1 := newNodeID(t).ID()
id2 := newNodeID(t).ID()
id3 := newNodeID(t).ID()
id4 := newNodeID(t).ID()
ftn1 := &fetcherTreeNode{
hash: common.HexToHash("1"),
td: big.NewInt(1),
}
ftn2 := &fetcherTreeNode{
hash: common.HexToHash("2"),
td: big.NewInt(2),
parent: ftn1,
}
ftn3 := &fetcherTreeNode{
hash: common.HexToHash("3"),
td: big.NewInt(3),
parent: ftn2,
}
lf := lightFetcher{
pm: &ProtocolManager{
ulc: &ulc{
trustedKeys: map[string]struct{}{
id1.String(): {},
id2.String(): {},
id3.String(): {},
id4.String(): {},
},
minTrustedFraction: 70,
},
},
maxConfirmedTd: ftn1.td,
peers: map[*peer]*fetcherPeerInfo{
{
id: "peer1",
Peer: p2p.NewPeer(id1, "peer1", []p2p.Cap{}),
isTrusted: true,
}: {
nodeByHash: map[common.Hash]*fetcherTreeNode{
ftn1.hash: ftn1,
ftn2.hash: ftn2,
},
},
{
Peer: p2p.NewPeer(id2, "peer2", []p2p.Cap{}),
id: "peer2",
isTrusted: true,
}: {
nodeByHash: map[common.Hash]*fetcherTreeNode{
ftn1.hash: ftn1,
ftn2.hash: ftn2,
},
},
{
id: "peer3",
Peer: p2p.NewPeer(id3, "peer3", []p2p.Cap{}),
isTrusted: true,
}: {
nodeByHash: map[common.Hash]*fetcherTreeNode{
ftn1.hash: ftn1,
ftn2.hash: ftn2,
ftn3.hash: ftn3,
},
},
{
id: "peer4",
Peer: p2p.NewPeer(id4, "peer4", []p2p.Cap{}),
isTrusted: true,
}: {
nodeByHash: map[common.Hash]*fetcherTreeNode{
ftn1.hash: ftn1,
},
},
},
chain: &lightChainStub{
tds: map[common.Hash]*big.Int{},
headers: map[common.Hash]*types.Header{
ftn1.hash: {},
ftn2.hash: {},
ftn3.hash: {},
},
},
}
bestHash, bestAmount, bestTD, sync := lf.findBestRequest()
if bestTD == nil {
t.Fatal("Empty result")
}
if bestTD.Cmp(ftn2.td) != 0 {
t.Fatal("bad td", bestTD)
}
if bestHash != ftn2.hash {
t.Fatal("bad hash", bestTD)
}
_, _ = bestAmount, sync
}
type lightChainStub struct {
BlockChain
tds map[common.Hash]*big.Int
headers map[common.Hash]*types.Header
insertHeaderChainAssertFunc func(chain []*types.Header, checkFreq int) (int, error)
}
func (l *lightChainStub) GetHeader(hash common.Hash, number uint64) *types.Header {
if h, ok := l.headers[hash]; ok {
return h
}
return nil
}
func (l *lightChainStub) LockChain() {}
func (l *lightChainStub) UnlockChain() {}
func (l *lightChainStub) GetTd(hash common.Hash, number uint64) *big.Int {
if td, ok := l.tds[hash]; ok {
return td
}
return nil
}
func (l *lightChainStub) InsertHeaderChain(chain []*types.Header, checkFreq int) (int, error) {
return l.insertHeaderChainAssertFunc(chain, checkFreq)
}
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)
}

View file

@ -69,7 +69,7 @@ const (
)
// newFreeClientPool creates a new free client pool
func newFreeClientPool(db ethdb.Database, freeClientCap uint64, totalLimit int, clock mclock.Clock, removePeer func(string), metricsLogger, eventLogger *csvlogger.Logger) *freeClientPool {
func newFreeClientPool(db ethdb.Database, freeClientCap uint64, totalLimit int, clock mclock.Clock, removePeer func(string), eventLogger *csvlogger.Logger, metricsLogger *csvlogger.Logger) *freeClientPool {
pool := &freeClientPool{
db: db,
clock: clock,

File diff suppressed because it is too large Load diff

View file

@ -48,11 +48,13 @@ func expectResponse(r p2p.MsgReader, msgcode, reqID, bv uint64, data interface{}
// Tests that block headers can be retrieved from a remote chain based on user queries.
func TestGetBlockHeadersLes2(t *testing.T) { testGetBlockHeaders(t, 2) }
func TestGetBlockHeadersLes3(t *testing.T) { testGetBlockHeaders(t, 3) }
func testGetBlockHeaders(t *testing.T, protocol int) {
server, tearDown := newServerEnv(t, downloader.MaxHashFetch+15, protocol, nil)
server, tearDown := newServerEnv(t, downloader.MaxHashFetch+15, protocol, nil, false, true, 0)
defer tearDown()
bc := server.pm.blockchain.(*core.BlockChain)
bc := server.handler.blockchain
// Create a "random" unknown hash for testing
var unknown common.Hash
@ -114,10 +116,10 @@ func testGetBlockHeaders(t *testing.T, protocol int) {
[]common.Hash{bc.CurrentBlock().Hash()},
},
// Ensure protocol limits are honored
/*{
&getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 1}, Amount: limit + 10, Reverse: true},
bc.GetBlockHashesFromHash(bc.CurrentBlock().Hash(), limit),
},*/
//{
// &getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 1}, Amount: limit + 10, Reverse: true},
// []common.Hash{},
//},
// Check that requesting more than available is handled gracefully
{
&getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 4}, Skip: 3, Amount: 3},
@ -165,9 +167,10 @@ func testGetBlockHeaders(t *testing.T, protocol int) {
}
// Send the hash request and verify the response
reqID++
cost := server.tPeer.GetRequestCost(GetBlockHeadersMsg, int(tt.query.Amount))
sendRequest(server.tPeer.app, GetBlockHeadersMsg, reqID, cost, tt.query)
if err := expectResponse(server.tPeer.app, BlockHeadersMsg, reqID, testBufLimit, headers); err != nil {
cost := server.peer.peer.GetRequestCost(GetBlockHeadersMsg, int(tt.query.Amount))
sendRequest(server.peer.app, GetBlockHeadersMsg, reqID, cost, tt.query)
if err := expectResponse(server.peer.app, BlockHeadersMsg, reqID, testBufLimit, headers); err != nil {
t.Errorf("test %d: headers mismatch: %v", i, err)
}
}
@ -175,11 +178,13 @@ func testGetBlockHeaders(t *testing.T, protocol int) {
// Tests that block contents can be retrieved from a remote chain based on their hashes.
func TestGetBlockBodiesLes2(t *testing.T) { testGetBlockBodies(t, 2) }
func TestGetBlockBodiesLes3(t *testing.T) { testGetBlockBodies(t, 3) }
func testGetBlockBodies(t *testing.T, protocol int) {
server, tearDown := newServerEnv(t, downloader.MaxBlockFetch+15, protocol, nil)
server, tearDown := newServerEnv(t, downloader.MaxBlockFetch+15, protocol, nil, false, true, 0)
defer tearDown()
bc := server.pm.blockchain.(*core.BlockChain)
bc := server.handler.blockchain
// Create a batch of tests for various scenarios
limit := MaxBodyFetch
@ -238,10 +243,11 @@ func testGetBlockBodies(t *testing.T, protocol int) {
}
}
reqID++
// Send the hash request and verify the response
cost := server.tPeer.GetRequestCost(GetBlockBodiesMsg, len(hashes))
sendRequest(server.tPeer.app, GetBlockBodiesMsg, reqID, cost, hashes)
if err := expectResponse(server.tPeer.app, BlockBodiesMsg, reqID, testBufLimit, bodies); err != nil {
cost := server.peer.peer.GetRequestCost(GetBlockBodiesMsg, len(hashes))
sendRequest(server.peer.app, GetBlockBodiesMsg, reqID, cost, hashes)
if err := expectResponse(server.peer.app, BlockBodiesMsg, reqID, testBufLimit, bodies); err != nil {
t.Errorf("test %d: bodies mismatch: %v", i, err)
}
}
@ -249,12 +255,13 @@ func testGetBlockBodies(t *testing.T, protocol int) {
// Tests that the contract codes can be retrieved based on account addresses.
func TestGetCodeLes2(t *testing.T) { testGetCode(t, 2) }
func TestGetCodeLes3(t *testing.T) { testGetCode(t, 3) }
func testGetCode(t *testing.T, protocol int) {
// Assemble the test environment
server, tearDown := newServerEnv(t, 4, protocol, nil)
server, tearDown := newServerEnv(t, 4, protocol, nil, false, true, 0)
defer tearDown()
bc := server.pm.blockchain.(*core.BlockChain)
bc := server.handler.blockchain
var codereqs []*CodeReq
var codes [][]byte
@ -270,21 +277,23 @@ func testGetCode(t *testing.T, protocol int) {
}
}
cost := server.tPeer.GetRequestCost(GetCodeMsg, len(codereqs))
sendRequest(server.tPeer.app, GetCodeMsg, 42, cost, codereqs)
if err := expectResponse(server.tPeer.app, CodeMsg, 42, testBufLimit, codes); err != nil {
cost := server.peer.peer.GetRequestCost(GetCodeMsg, len(codereqs))
sendRequest(server.peer.app, GetCodeMsg, 42, cost, codereqs)
if err := expectResponse(server.peer.app, CodeMsg, 42, testBufLimit, codes); err != nil {
t.Errorf("codes mismatch: %v", err)
}
}
// Tests that the transaction receipts can be retrieved based on hashes.
func TestGetReceiptLes2(t *testing.T) { testGetReceipt(t, 2) }
func TestGetReceiptLes3(t *testing.T) { testGetReceipt(t, 3) }
func testGetReceipt(t *testing.T, protocol int) {
// Assemble the test environment
server, tearDown := newServerEnv(t, 4, protocol, nil)
server, tearDown := newServerEnv(t, 4, protocol, nil, false, true, 0)
defer tearDown()
bc := server.pm.blockchain.(*core.BlockChain)
bc := server.handler.blockchain
// Collect the hashes to request, and the response to expect
hashes, receipts := []common.Hash{}, []types.Receipts{}
@ -295,26 +304,28 @@ func testGetReceipt(t *testing.T, protocol int) {
receipts = append(receipts, rawdb.ReadRawReceipts(server.db, block.Hash(), block.NumberU64()))
}
// Send the hash request and verify the response
cost := server.tPeer.GetRequestCost(GetReceiptsMsg, len(hashes))
sendRequest(server.tPeer.app, GetReceiptsMsg, 42, cost, hashes)
if err := expectResponse(server.tPeer.app, ReceiptsMsg, 42, testBufLimit, receipts); err != nil {
cost := server.peer.peer.GetRequestCost(GetReceiptsMsg, len(hashes))
sendRequest(server.peer.app, GetReceiptsMsg, 42, cost, hashes)
if err := expectResponse(server.peer.app, ReceiptsMsg, 42, testBufLimit, receipts); err != nil {
t.Errorf("receipts mismatch: %v", err)
}
}
// Tests that trie merkle proofs can be retrieved
func TestGetProofsLes2(t *testing.T) { testGetProofs(t, 2) }
func TestGetProofsLes3(t *testing.T) { testGetProofs(t, 3) }
func testGetProofs(t *testing.T, protocol int) {
// Assemble the test environment
server, tearDown := newServerEnv(t, 4, protocol, nil)
server, tearDown := newServerEnv(t, 4, protocol, nil, false, true, 0)
defer tearDown()
bc := server.pm.blockchain.(*core.BlockChain)
bc := server.handler.blockchain
var proofreqs []ProofReq
proofsV2 := light.NewNodeSet()
accounts := []common.Address{bankAddr, userAddr1, userAddr2, {}}
accounts := []common.Address{bankAddr, userAddr1, userAddr2, signerAddr, {}}
for i := uint64(0); i <= bc.CurrentBlock().NumberU64(); i++ {
header := bc.GetHeaderByNumber(i)
trie, _ := trie.New(header.Root, trie.NewDatabase(server.db))
@ -329,15 +340,16 @@ func testGetProofs(t *testing.T, protocol int) {
}
}
// Send the proof request and verify the response
cost := server.tPeer.GetRequestCost(GetProofsV2Msg, len(proofreqs))
sendRequest(server.tPeer.app, GetProofsV2Msg, 42, cost, proofreqs)
if err := expectResponse(server.tPeer.app, ProofsV2Msg, 42, testBufLimit, proofsV2.NodeList()); err != nil {
cost := server.peer.peer.GetRequestCost(GetProofsV2Msg, len(proofreqs))
sendRequest(server.peer.app, GetProofsV2Msg, 42, cost, proofreqs)
if err := expectResponse(server.peer.app, ProofsV2Msg, 42, testBufLimit, proofsV2.NodeList()); err != nil {
t.Errorf("proofs mismatch: %v", err)
}
}
// Tests that CHT proofs can be correctly retrieved.
func TestGetCHTProofsLes2(t *testing.T) { testGetCHTProofs(t, 2) }
func TestGetCHTProofsLes3(t *testing.T) { testGetCHTProofs(t, 3) }
func testGetCHTProofs(t *testing.T, protocol int) {
config := light.TestServerIndexerConfig
@ -351,9 +363,10 @@ func testGetCHTProofs(t *testing.T, protocol int) {
time.Sleep(10 * time.Millisecond)
}
}
server, tearDown := newServerEnv(t, int(config.ChtSize+config.ChtConfirms), protocol, waitIndexers)
server, tearDown := newServerEnv(t, int(config.ChtSize+config.ChtConfirms), protocol, waitIndexers, false, true, 0)
defer tearDown()
bc := server.pm.blockchain.(*core.BlockChain)
bc := server.handler.blockchain
// Assemble the proofs from the different protocols
header := bc.GetHeaderByNumber(config.ChtSize - 1)
@ -376,15 +389,18 @@ func testGetCHTProofs(t *testing.T, protocol int) {
AuxReq: auxHeader,
}}
// Send the proof request and verify the response
cost := server.tPeer.GetRequestCost(GetHelperTrieProofsMsg, len(requestsV2))
sendRequest(server.tPeer.app, GetHelperTrieProofsMsg, 42, cost, requestsV2)
if err := expectResponse(server.tPeer.app, HelperTrieProofsMsg, 42, testBufLimit, proofsV2); err != nil {
cost := server.peer.peer.GetRequestCost(GetHelperTrieProofsMsg, len(requestsV2))
sendRequest(server.peer.app, GetHelperTrieProofsMsg, 42, cost, requestsV2)
if err := expectResponse(server.peer.app, HelperTrieProofsMsg, 42, testBufLimit, proofsV2); err != nil {
t.Errorf("proofs mismatch: %v", err)
}
}
func TestGetBloombitsProofsLes2(t *testing.T) { testGetBloombitsProofs(t, 2) }
func TestGetBloombitsProofsLes3(t *testing.T) { testGetBloombitsProofs(t, 3) }
// Tests that bloombits proofs can be correctly retrieved.
func TestGetBloombitsProofs(t *testing.T) {
func testGetBloombitsProofs(t *testing.T, protocol int) {
config := light.TestServerIndexerConfig
waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) {
@ -396,9 +412,10 @@ func TestGetBloombitsProofs(t *testing.T) {
time.Sleep(10 * time.Millisecond)
}
}
server, tearDown := newServerEnv(t, int(config.BloomTrieSize+config.BloomTrieConfirms), 2, waitIndexers)
server, tearDown := newServerEnv(t, int(config.BloomTrieSize+config.BloomTrieConfirms), protocol, waitIndexers, false, true, 0)
defer tearDown()
bc := server.pm.blockchain.(*core.BlockChain)
bc := server.handler.blockchain
// Request and verify each bit of the bloom bits proofs
for bit := 0; bit < 2048; bit++ {
@ -421,42 +438,38 @@ func TestGetBloombitsProofs(t *testing.T) {
trie.Prove(key, 0, &proofs.Proofs)
// Send the proof request and verify the response
cost := server.tPeer.GetRequestCost(GetHelperTrieProofsMsg, len(requests))
sendRequest(server.tPeer.app, GetHelperTrieProofsMsg, 42, cost, requests)
if err := expectResponse(server.tPeer.app, HelperTrieProofsMsg, 42, testBufLimit, proofs); err != nil {
cost := server.peer.peer.GetRequestCost(GetHelperTrieProofsMsg, len(requests))
sendRequest(server.peer.app, GetHelperTrieProofsMsg, 42, cost, requests)
if err := expectResponse(server.peer.app, HelperTrieProofsMsg, 42, testBufLimit, proofs); err != nil {
t.Errorf("bit %d: proofs mismatch: %v", bit, err)
}
}
}
func TestTransactionStatusLes2(t *testing.T) {
server, tearDown := newServerEnv(t, 0, 2, nil)
func TestTransactionStatusLes2(t *testing.T) { testTransactionStatus(t, 2) }
func TestTransactionStatusLes3(t *testing.T) { testTransactionStatus(t, 3) }
func testTransactionStatus(t *testing.T, protocol int) {
server, tearDown := newServerEnv(t, 0, protocol, nil, false, true, 0)
defer tearDown()
chain := server.pm.blockchain.(*core.BlockChain)
config := core.DefaultTxPoolConfig
config.Journal = ""
txpool := core.NewTxPool(config, params.TestChainConfig, chain)
server.pm.txpool = txpool
peer, _ := newTestPeer(t, "peer", 2, server.pm, true, 0)
defer peer.close()
chain := server.handler.blockchain
var reqID uint64
test := func(tx *types.Transaction, send bool, expStatus light.TxStatus) {
reqID++
if send {
cost := server.tPeer.GetRequestCost(SendTxV2Msg, 1)
sendRequest(server.tPeer.app, SendTxV2Msg, reqID, cost, types.Transactions{tx})
cost := server.peer.peer.GetRequestCost(SendTxV2Msg, 1)
sendRequest(server.peer.app, SendTxV2Msg, reqID, cost, types.Transactions{tx})
} else {
cost := server.tPeer.GetRequestCost(GetTxStatusMsg, 1)
sendRequest(server.tPeer.app, GetTxStatusMsg, reqID, cost, []common.Hash{tx.Hash()})
cost := server.peer.peer.GetRequestCost(GetTxStatusMsg, 1)
sendRequest(server.peer.app, GetTxStatusMsg, reqID, cost, []common.Hash{tx.Hash()})
}
if err := expectResponse(server.tPeer.app, TxStatusMsg, reqID, testBufLimit, []light.TxStatus{expStatus}); err != nil {
if err := expectResponse(server.peer.app, TxStatusMsg, reqID, testBufLimit, []light.TxStatus{expStatus}); err != nil {
t.Errorf("transaction status mismatch")
}
}
signer := types.HomesteadSigner{}
// test error status by sending an underpriced transaction
@ -486,18 +499,22 @@ func TestTransactionStatusLes2(t *testing.T) {
}
// wait until TxPool processes the inserted block
for i := 0; i < 10; i++ {
if pending, _ := txpool.Stats(); pending == 1 {
if pending, _ := server.handler.txpool.Stats(); pending == 1 {
break
}
time.Sleep(100 * time.Millisecond)
}
if pending, _ := txpool.Stats(); pending != 1 {
if pending, _ := server.handler.txpool.Stats(); pending != 1 {
t.Fatalf("pending count mismatch: have %d, want 1", pending)
}
// Discard new block announcement
msg, _ := server.peer.app.ReadMsg()
msg.Discard()
// check if their status is included now
block1hash := rawdb.ReadCanonicalHash(server.db, 1)
test(tx1, false, light.TxStatus{Status: core.TxStatusIncluded, Lookup: &rawdb.LegacyTxLookupEntry{BlockHash: block1hash, BlockIndex: 1, Index: 0}})
test(tx2, false, light.TxStatus{Status: core.TxStatusIncluded, Lookup: &rawdb.LegacyTxLookupEntry{BlockHash: block1hash, BlockIndex: 1, Index: 1}})
// create a reorg that rolls them back
@ -507,44 +524,44 @@ func TestTransactionStatusLes2(t *testing.T) {
}
// wait until TxPool processes the reorg
for i := 0; i < 10; i++ {
if pending, _ := txpool.Stats(); pending == 3 {
if pending, _ := server.handler.txpool.Stats(); pending == 3 {
break
}
time.Sleep(100 * time.Millisecond)
}
if pending, _ := txpool.Stats(); pending != 3 {
if pending, _ := server.handler.txpool.Stats(); pending != 3 {
t.Fatalf("pending count mismatch: have %d, want 3", pending)
}
// Discard new block announcement
msg, _ = server.peer.app.ReadMsg()
msg.Discard()
// check if their status is pending again
test(tx1, false, light.TxStatus{Status: core.TxStatusPending})
test(tx2, false, light.TxStatus{Status: core.TxStatusPending})
}
func TestStopResumeLes3(t *testing.T) {
db := rawdb.NewMemoryDatabase()
clock := &mclock.Simulated{}
testCost := testBufLimit / 10
pm, _, err := newTestProtocolManager(false, 0, nil, nil, nil, db, nil, testCost, clock)
if err != nil {
t.Fatalf("Failed to create protocol manager: %v", err)
}
peer, _ := newTestPeer(t, "peer", 3, pm, true, testCost)
defer peer.close()
server, tearDown := newServerEnv(t, 0, 3, nil, true, true, testBufLimit/10)
defer tearDown()
expBuf := testBufLimit
var reqID uint64
server.handler.server.costTracker.disableRealCost = true
var (
reqID uint64
expBuf = testBufLimit
testCost = testBufLimit / 10
)
req := func() {
reqID++
sendRequest(peer.app, GetBlockHeadersMsg, reqID, testCost, &getBlockHeadersData{Origin: hashOrNumber{Hash: common.Hash{1}}, Amount: 1})
sendRequest(server.peer.app, GetBlockHeadersMsg, reqID, testCost, &getBlockHeadersData{Origin: hashOrNumber{Hash: common.Hash{1}}, Amount: 1})
}
for i := 1; i <= 5; i++ {
// send requests while we still have enough buffer and expect a response
for expBuf >= testCost {
req()
expBuf -= testCost
if err := expectResponse(peer.app, BlockHeadersMsg, reqID, expBuf, nil); err != nil {
if err := expectResponse(server.peer.app, BlockHeadersMsg, reqID, expBuf, nil); err != nil {
t.Errorf("expected response and failed: %v", err)
}
}
@ -554,15 +571,16 @@ func TestStopResumeLes3(t *testing.T) {
req()
c--
}
if err := p2p.ExpectMsg(peer.app, StopMsg, nil); err != nil {
if err := p2p.ExpectMsg(server.peer.app, StopMsg, nil); err != nil {
t.Errorf("expected StopMsg and failed: %v", err)
}
// wait until the buffer is recharged by half of the limit
wait := testBufLimit / testBufRecharge / 2
clock.Run(time.Millisecond * time.Duration(wait))
server.clock.(*mclock.Simulated).Run(time.Millisecond * time.Duration(wait))
// expect a ResumeMsg with the partially recharged buffer value
expBuf += testBufRecharge * wait
if err := p2p.ExpectMsg(peer.app, ResumeMsg, expBuf); err != nil {
if err := p2p.ExpectMsg(server.peer.app, ResumeMsg, expBuf); err != nil {
t.Errorf("expected ResumeMsg and failed: %v", err)
}
}

View file

@ -108,7 +108,7 @@ func (odr *LesOdr) Retrieve(ctx context.Context, req light.OdrRequest) (err erro
},
canSend: func(dp distPeer) bool {
p := dp.(*peer)
if !p.isOnlyAnnounce {
if !p.announceOnly {
return lreq.CanSend(p)
}
return false

View file

@ -39,6 +39,7 @@ import (
type odrTestFn func(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte
func TestOdrGetBlockLes2(t *testing.T) { testOdr(t, 2, 1, true, odrGetBlock) }
func TestOdrGetBlockLes3(t *testing.T) { testOdr(t, 3, 1, true, odrGetBlock) }
func odrGetBlock(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte {
var block *types.Block
@ -55,6 +56,7 @@ func odrGetBlock(ctx context.Context, db ethdb.Database, config *params.ChainCon
}
func TestOdrGetReceiptsLes2(t *testing.T) { testOdr(t, 2, 1, true, odrGetReceipts) }
func TestOdrGetReceiptsLes3(t *testing.T) { testOdr(t, 3, 1, true, odrGetReceipts) }
func odrGetReceipts(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte {
var receipts types.Receipts
@ -75,6 +77,7 @@ func odrGetReceipts(ctx context.Context, db ethdb.Database, config *params.Chain
}
func TestOdrAccountsLes2(t *testing.T) { testOdr(t, 2, 1, true, odrAccounts) }
func TestOdrAccountsLes3(t *testing.T) { testOdr(t, 3, 1, true, odrAccounts) }
func odrAccounts(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte {
dummyAddr := common.HexToAddress("1234567812345678123456781234567812345678")
@ -103,6 +106,7 @@ func odrAccounts(ctx context.Context, db ethdb.Database, config *params.ChainCon
}
func TestOdrContractCallLes2(t *testing.T) { testOdr(t, 2, 2, true, odrContractCall) }
func TestOdrContractCallLes3(t *testing.T) { testOdr(t, 3, 2, true, odrContractCall) }
type callmsg struct {
types.Message
@ -152,6 +156,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai
}
func TestOdrTxStatusLes2(t *testing.T) { testOdr(t, 2, 1, false, odrTxStatus) }
func TestOdrTxStatusLes3(t *testing.T) { testOdr(t, 3, 1, false, odrTxStatus) }
func odrTxStatus(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte {
var txs types.Transactions
@ -178,21 +183,22 @@ func odrTxStatus(ctx context.Context, db ethdb.Database, config *params.ChainCon
// testOdr tests odr requests whose validation guaranteed by block headers.
func testOdr(t *testing.T, protocol int, expFail uint64, checkCached bool, fn odrTestFn) {
// Assemble the test environment
server, client, tearDown := newClientServerEnv(t, 4, protocol, nil, true)
server, client, tearDown := newClientServerEnv(t, 4, protocol, nil, nil, false, true)
defer tearDown()
client.pm.synchronise(client.rPeer)
client.handler.synchronise(client.peer.peer)
test := func(expFail uint64) {
// Mark this as a helper to put the failures at the correct lines
t.Helper()
for i := uint64(0); i <= server.pm.blockchain.CurrentHeader().Number.Uint64(); i++ {
for i := uint64(0); i <= server.handler.blockchain.CurrentHeader().Number.Uint64(); i++ {
bhash := rawdb.ReadCanonicalHash(server.db, i)
b1 := fn(light.NoOdr, server.db, server.pm.chainConfig, server.pm.blockchain.(*core.BlockChain), nil, bhash)
b1 := fn(light.NoOdr, server.db, server.handler.server.chainConfig, server.handler.blockchain, nil, bhash)
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
defer cancel()
b2 := fn(ctx, client.db, client.pm.chainConfig, nil, client.pm.blockchain.(*light.LightChain), bhash)
b2 := fn(ctx, client.db, client.handler.backend.chainConfig, nil, client.handler.backend.blockchain, bhash)
cancel()
eq := bytes.Equal(b1, b2)
exp := i < expFail
@ -204,22 +210,22 @@ func testOdr(t *testing.T, protocol int, expFail uint64, checkCached bool, fn od
}
}
}
// temporarily remove peer to test odr fails
// expect retrievals to fail (except genesis block) without a les peer
client.peers.Unregister(client.rPeer.id)
time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed
client.handler.backend.peers.lock.Lock()
client.peer.peer.hasBlock = func(common.Hash, uint64, bool) bool { return false }
client.handler.backend.peers.lock.Unlock()
test(expFail)
// expect all retrievals to pass
client.peers.Register(client.rPeer)
time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed
client.peers.lock.Lock()
client.rPeer.hasBlock = func(common.Hash, uint64, bool) bool { return true }
client.peers.lock.Unlock()
client.handler.backend.peers.lock.Lock()
client.peer.peer.hasBlock = func(common.Hash, uint64, bool) bool { return true }
client.handler.backend.peers.lock.Unlock()
test(5)
// still expect all retrievals to pass, now data should be cached locally
if checkCached {
// still expect all retrievals to pass, now data should be cached locally
client.peers.Unregister(client.rPeer.id)
client.handler.backend.peers.Unregister(client.peer.peer.id)
time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed
test(5)
}

View file

@ -108,21 +108,21 @@ type peer struct {
fcParams flowcontrol.ServerParams
fcCosts requestCostTable
isTrusted bool
isOnlyAnnounce bool
trusted bool
announceOnly bool
chainSince, chainRecent uint64
stateSince, stateRecent uint64
}
func newPeer(version int, network uint64, isTrusted bool, p *p2p.Peer, rw p2p.MsgReadWriter) *peer {
func newPeer(version int, network uint64, trusted bool, p *p2p.Peer, rw p2p.MsgReadWriter) *peer {
return &peer{
Peer: p,
rw: rw,
version: version,
network: network,
id: fmt.Sprintf("%x", p.ID().Bytes()),
isTrusted: isTrusted,
errCh: make(chan error, 1),
Peer: p,
rw: rw,
version: version,
network: network,
id: fmt.Sprintf("%x", p.ID().Bytes()),
trusted: trusted,
errCh: make(chan error, 1),
}
}
@ -266,6 +266,14 @@ func (p *peer) updateCapacity(cap uint64) {
p.queueSend(func() { p.SendAnnounce(announceData{Update: kvList}) })
}
func (p *peer) responseID() uint64 {
p.responseLock.Lock()
defer p.responseLock.Unlock()
p.responseCount += 1
return p.responseCount
}
func sendRequest(w p2p.MsgWriter, msgcode, reqID, cost uint64, data interface{}) error {
type req struct {
ReqID uint64
@ -348,6 +356,7 @@ func (p *peer) HasBlock(hash common.Hash, number uint64, hasState bool) bool {
}
hasBlock := p.hasBlock
p.lock.RUnlock()
return head >= number && number >= since && (recent == 0 || number+recent+4 > head) && hasBlock != nil && hasBlock(hash, number, hasState)
}
@ -546,6 +555,8 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
defer p.lock.Unlock()
var send keyValueList
// Add some basic handshake fields
send = send.add("protocolVersion", uint64(p.version))
send = send.add("networkId", p.network)
send = send.add("headTd", td)
@ -553,7 +564,8 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
send = send.add("headNum", headNum)
send = send.add("genesisHash", genesis)
if server != nil {
if !server.onlyAnnounce {
// Add some information which services server can offer.
if !server.config.OnlyAnnounce {
send = send.add("serveHeaders", nil)
send = send.add("serveChainSince", uint64(0))
send = send.add("serveStateSince", uint64(0))
@ -562,27 +574,29 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
}
send = send.add("flowControl/BL", server.defParams.BufLimit)
send = send.add("flowControl/MRR", server.defParams.MinRecharge)
var costList RequestCostList
if server.costTracker != nil {
costList = server.costTracker.makeCostList(server.costTracker.globalFactor())
} else {
costList = testCostList(server.testCost)
costList := server.costTracker.makeCostList(server.costTracker.globalFactor())
// Generate some fake cost list for testing purpose.
if server.costTracker.costListHook != nil {
costList = server.costTracker.costListHook()
}
send = send.add("flowControl/MRC", costList)
p.fcCosts = costList.decode(ProtocolLengths[uint(p.version)])
p.fcParams = server.defParams
if server.protocolManager != nil && server.protocolManager.reg != nil && server.protocolManager.reg.isRunning() {
cp, height := server.protocolManager.reg.stableCheckpoint()
// Add advertised checkpoint and register block height which
// client can verify the checkpoint validity.
if server.registrar != nil && server.registrar.isRunning() {
cp, height := server.registrar.stableCheckpoint()
if cp != nil {
send = send.add("checkpoint/value", cp)
send = send.add("checkpoint/registerHeight", height)
}
}
} else {
//on client node
// Add some client-specific handshake fields
p.announceType = announceTypeSimple
if p.isTrusted {
if p.trusted {
p.announceType = announceTypeSigned
}
send = send.add("announceType", p.announceType)
@ -631,34 +645,29 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
}
if server != nil {
// until we have a proper peer connectivity API, allow LES connection to other servers
/*if recv.get("serveStateSince", nil) == nil {
return errResp(ErrUselessPeer, "wanted client, got server")
}*/
if recv.get("announceType", &p.announceType) != nil {
//set default announceType on server side
// set default announceType on server side
p.announceType = announceTypeSimple
}
p.fcClient = flowcontrol.NewClientNode(server.fcManager, server.defParams)
} else {
//mark OnlyAnnounce server if "serveHeaders", "serveChainSince", "serveStateSince" or "txRelay" fields don't exist
if recv.get("serveChainSince", &p.chainSince) != nil {
p.isOnlyAnnounce = true
p.announceOnly = true
}
if recv.get("serveRecentChain", &p.chainRecent) != nil {
p.chainRecent = 0
}
if recv.get("serveStateSince", &p.stateSince) != nil {
p.isOnlyAnnounce = true
p.announceOnly = true
}
if recv.get("serveRecentState", &p.stateRecent) != nil {
p.stateRecent = 0
}
if recv.get("txRelay", nil) != nil {
p.isOnlyAnnounce = true
p.announceOnly = true
}
if p.isOnlyAnnounce && !p.isTrusted {
if p.announceOnly && !p.trusted {
return errResp(ErrUselessPeer, "peer cannot serve requests")
}
@ -696,7 +705,7 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
}
recv.get("checkpoint/registerHeight", &p.registeredHeight)
if !p.isOnlyAnnounce {
if !p.announceOnly {
for msgCode := range reqAvgTimeCost {
if p.fcCosts[msgCode] == nil {
return errResp(ErrUselessPeer, "peer does not support message %d", msgCode)
@ -714,15 +723,10 @@ func (p *peer) updateFlowControl(update keyValueMap) {
if p.fcServer == nil {
return
}
params := p.fcParams
updateParams := false
if update.get("flowControl/BL", &params.BufLimit) == nil {
updateParams = true
}
if update.get("flowControl/MRR", &params.MinRecharge) == nil {
updateParams = true
}
if updateParams {
// If any of the flow control params is nil, refuse to update.
var params flowcontrol.ServerParams
if update.get("flowControl/BL", &params.BufLimit) == nil && update.get("flowControl/MRR", &params.MinRecharge) == nil {
// todo can light client set a minimal acceptable flow control params?
p.fcParams = params
p.fcServer.UpdateParams(params)
}

View file

@ -1,48 +1,71 @@
// Copyright 2019 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 <http://www.gnu.org/licenses/>.
package les
import (
"math/big"
"net"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/crypto"
"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/enode"
"github.com/ethereum/go-ethereum/rlp"
)
const (
test_networkid = 10
protocol_version = lpv2
)
const protocolVersion = lpv2
var (
hash = common.HexToHash("some string")
genesis = common.HexToHash("genesis hash")
hash = common.HexToHash("deadbeef")
genesis = common.HexToHash("cafebabe")
headNum = uint64(1234)
td = big.NewInt(123)
)
//ulc connects to trusted peer and send announceType=announceTypeSigned
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 TestPeerHandshakeSetAnnounceTypeToAnnounceTypeSignedForTrustedPeer(t *testing.T) {
id := newNodeID(t).ID()
//peer to connect(on ulc side)
// peer to connect(on ulc side)
p := peer{
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
version: protocol_version,
isTrusted: true,
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
version: protocolVersion,
trusted: true,
rw: &rwStub{
WriteHook: func(recvList keyValueList) {
//checking that ulc sends to peer allowedRequests=onlyAnnounceRequests 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 announceTypeSigned")
}
@ -55,18 +78,15 @@ func TestPeerHandshakeSetAnnounceTypeToAnnounceTypeSignedForTrustedPeer(t *testi
l = l.add("flowControl/BL", uint64(0))
l = l.add("flowControl/MRR", uint64(0))
l = l.add("flowControl/MRC", testCostList(0))
return l
},
},
network: test_networkid,
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")
}
@ -76,18 +96,16 @@ func TestPeerHandshakeAnnounceTypeSignedForTrustedPeersPeerNotInTrusted(t *testi
id := newNodeID(t).ID()
p := peer{
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
version: protocol_version,
version: protocolVersion,
rw: &rwStub{
WriteHook: func(recvList keyValueList) {
//checking that ulc sends to peer allowedRequests=noRequests and announceType != announceTypeSigned
// 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")
}
@ -100,13 +118,11 @@ func TestPeerHandshakeAnnounceTypeSignedForTrustedPeersPeerNotInTrusted(t *testi
l = l.add("flowControl/BL", uint64(0))
l = l.add("flowControl/MRR", uint64(0))
l = l.add("flowControl/MRC", testCostList(0))
return l
},
},
network: test_networkid,
network: NetworkId,
}
err := p.Handshake(td, hash, headNum, genesis, nil)
if err != nil {
t.Fatal(err)
@ -123,24 +139,22 @@ func TestPeerHandshakeDefaultAllRequests(t *testing.T) {
p := peer{
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
version: protocol_version,
version: protocolVersion,
rw: &rwStub{
ReadHook: func(l keyValueList) keyValueList {
l = l.add("announceType", uint64(announceTypeSigned))
l = l.add("allowedRequests", uint64(0))
return l
},
},
network: test_networkid,
network: NetworkId,
}
err := p.Handshake(td, hash, headNum, genesis, s)
if err != nil {
t.Fatal(err)
}
if p.isOnlyAnnounce {
if p.announceOnly {
t.Fatal("Incorrect announceType")
}
}
@ -149,15 +163,14 @@ func TestPeerHandshakeServerSendOnlyAnnounceRequestsHeaders(t *testing.T) {
id := newNodeID(t).ID()
s := generateLesServer()
s.onlyAnnounce = true
s.config.OnlyAnnounce = true
p := peer{
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
version: protocol_version,
version: protocolVersion,
rw: &rwStub{
ReadHook: func(l keyValueList) keyValueList {
l = l.add("announceType", uint64(announceTypeSigned))
return l
},
WriteHook: func(l keyValueList) {
@ -171,7 +184,7 @@ func TestPeerHandshakeServerSendOnlyAnnounceRequestsHeaders(t *testing.T) {
}
},
},
network: test_networkid,
network: NetworkId,
}
err := p.Handshake(td, hash, headNum, genesis, s)
@ -184,7 +197,7 @@ func TestPeerHandshakeClientReceiveOnlyAnnounceRequestsHeaders(t *testing.T) {
p := peer{
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
version: protocol_version,
version: protocolVersion,
rw: &rwStub{
ReadHook: func(l keyValueList) keyValueList {
l = l.add("flowControl/BL", uint64(0))
@ -196,8 +209,8 @@ func TestPeerHandshakeClientReceiveOnlyAnnounceRequestsHeaders(t *testing.T) {
return l
},
},
network: test_networkid,
isTrusted: true,
network: NetworkId,
trusted: true,
}
err := p.Handshake(td, hash, headNum, genesis, nil)
@ -205,7 +218,7 @@ func TestPeerHandshakeClientReceiveOnlyAnnounceRequestsHeaders(t *testing.T) {
t.Fatal(err)
}
if !p.isOnlyAnnounce {
if !p.announceOnly {
t.Fatal("isOnlyAnnounce must be true")
}
}
@ -215,19 +228,17 @@ func TestPeerHandshakeClientReturnErrorOnUselessPeer(t *testing.T) {
p := peer{
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
version: protocol_version,
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: test_networkid,
network: NetworkId,
}
err := p.Handshake(td, hash, headNum, genesis, nil)
@ -238,12 +249,16 @@ func TestPeerHandshakeClientReturnErrorOnUselessPeer(t *testing.T) {
func generateLesServer() *LesServer {
s := &LesServer{
lesCommons: lesCommons{
config: &eth.Config{OnlyAnnounce: true},
},
defParams: flowcontrol.ServerParams{
BufLimit: uint64(300000000),
MinRecharge: uint64(50000),
},
fcManager: flowcontrol.NewClientManager(nil, &mclock.System{}),
}
s.costTracker, _ = newCostTracker(rawdb.NewMemoryDatabase(), s.config, nil)
return s
}
@ -254,8 +269,8 @@ type rwStub struct {
func (s *rwStub) ReadMsg() (p2p.Msg, error) {
payload := keyValueList{}
payload = payload.add("protocolVersion", uint64(protocol_version))
payload = payload.add("networkId", uint64(test_networkid))
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)
@ -264,12 +279,10 @@ func (s *rwStub) ReadMsg() (p2p.Msg, error) {
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,
@ -281,10 +294,8 @@ func (s *rwStub) WriteMsg(m p2p.Msg) error {
if err := m.Decode(&recvList); err != nil {
return err
}
if s.WriteHook != nil {
s.WriteHook(recvList)
}
return nil
}

View file

@ -37,18 +37,21 @@ func secAddr(addr common.Address) []byte {
type accessTestFn func(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest
func TestBlockAccessLes2(t *testing.T) { testAccess(t, 2, tfBlockAccess) }
func TestBlockAccessLes3(t *testing.T) { testAccess(t, 3, tfBlockAccess) }
func tfBlockAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest {
return &light.BlockRequest{Hash: bhash, Number: number}
}
func TestReceiptsAccessLes2(t *testing.T) { testAccess(t, 2, tfReceiptsAccess) }
func TestReceiptsAccessLes3(t *testing.T) { testAccess(t, 3, tfReceiptsAccess) }
func tfReceiptsAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest {
return &light.ReceiptsRequest{Hash: bhash, Number: number}
}
func TestTrieEntryAccessLes2(t *testing.T) { testAccess(t, 2, tfTrieEntryAccess) }
func TestTrieEntryAccessLes3(t *testing.T) { testAccess(t, 3, tfTrieEntryAccess) }
func tfTrieEntryAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest {
if number := rawdb.ReadHeaderNumber(db, bhash); number != nil {
@ -58,6 +61,7 @@ func tfTrieEntryAccess(db ethdb.Database, bhash common.Hash, number uint64) ligh
}
func TestCodeAccessLes2(t *testing.T) { testAccess(t, 2, tfCodeAccess) }
func TestCodeAccessLes3(t *testing.T) { testAccess(t, 3, tfCodeAccess) }
func tfCodeAccess(db ethdb.Database, bhash common.Hash, num uint64) light.OdrRequest {
number := rawdb.ReadHeaderNumber(db, bhash)
@ -75,17 +79,18 @@ func tfCodeAccess(db ethdb.Database, bhash common.Hash, num uint64) light.OdrReq
func testAccess(t *testing.T, protocol int, fn accessTestFn) {
// Assemble the test environment
server, client, tearDown := newClientServerEnv(t, 4, protocol, nil, true)
server, client, tearDown := newClientServerEnv(t, 4, protocol, nil, nil, false, true)
defer tearDown()
client.pm.synchronise(client.rPeer)
client.handler.synchronise(client.peer.peer)
test := func(expFail uint64) {
for i := uint64(0); i <= server.pm.blockchain.CurrentHeader().Number.Uint64(); i++ {
for i := uint64(0); i <= server.handler.blockchain.CurrentHeader().Number.Uint64(); i++ {
bhash := rawdb.ReadCanonicalHash(server.db, i)
if req := fn(client.db, bhash, i); req != nil {
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
defer cancel()
err := client.pm.odr.Retrieve(ctx, req)
err := client.handler.backend.odr.Retrieve(ctx, req)
cancel()
got := err == nil
exp := i < expFail
if exp && !got {
@ -97,18 +102,5 @@ func testAccess(t *testing.T, protocol int, fn accessTestFn) {
}
}
}
// temporarily remove peer to test odr fails
client.peers.Unregister(client.rPeer.id)
time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed
// expect retrievals to fail (except genesis block) without a les peer
test(0)
client.peers.Register(client.rPeer)
time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed
client.rPeer.lock.Lock()
client.rPeer.hasBlock = func(common.Hash, uint64, bool) bool { return true }
client.rPeer.lock.Unlock()
// expect all retrievals to pass
test(5)
}

View file

@ -18,15 +18,12 @@ package les
import (
"crypto/ecdsa"
"sync"
"fmt"
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"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/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/les/csvlogger"
"github.com/ethereum/go-ethereum/les/flowcontrol"
@ -34,101 +31,124 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discv5"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
)
const bufLimitRatio = 6000 // fixed bufLimit/MRR ratio
const (
logFileName = "" // csv log file name (disabled if empty)
logClientPoolMetrics = true // log client pool metrics
logClientPoolEvents = false // detailed client pool event logging
logRequestServing = true // log request serving metrics and events
logBlockProcEvents = true // log block processing events
logProtocolHandler = true // log protocol handler events
logRequestServing = true // log request serving metrics and events
logBlockProcEvents = true // log block processing events
logHandlerEvents = true // log protocol handler events
)
type LesServer struct {
lesCommons
fcManager *flowcontrol.ClientManager // nil if our node is client only
costTracker *costTracker
testCost uint64
defParams flowcontrol.ServerParams
lesTopics []discv5.Topic
privateKey *ecdsa.PrivateKey
quitSync chan struct{}
onlyAnnounce bool
csvLogger *csvlogger.Logger
logTotalCap *csvlogger.Channel
handler *serverHandler
lesTopics []discv5.Topic
privateKey *ecdsa.PrivateKey
thcNormal, thcBlockProcessing int // serving thread count for normal operation and block processing mode
// Flow control and capacity management
fcManager *flowcontrol.ClientManager
costTracker *costTracker
defParams flowcontrol.ServerParams
servingQueue *servingQueue
freeClientPool *freeClientPool
priorityClientPool *priorityClientPool
maxPeers int
minCapacity, freeClientCap uint64
freeClientPool *freeClientPool
priorityClientPool *priorityClientPool
minCapacity uint64
freeClientCap uint64
threadsIdle int // Request serving threads count when system is idle.
threadsBusy int // Request serving threads count when system is busy(block insertion).
logger *csvlogger.Logger // System metrics and events logger
}
func NewLesServer(e *eth.Ethereum, config *eth.Config) (*LesServer, error) {
var csvLogger *csvlogger.Logger
if logFileName != "" {
csvLogger = csvlogger.NewLogger(logFileName, time.Second*10, "event, peerId")
}
requestLogger := csvLogger
if !logRequestServing {
requestLogger = nil
}
// Collect les protocol version information supported by local node.
lesTopics := make([]discv5.Topic, len(AdvertiseProtocolVersions))
for i, pv := range AdvertiseProtocolVersions {
lesTopics[i] = lesTopic(e.BlockChain().Genesis().Hash(), pv)
}
quitSync := make(chan struct{})
// Calculate the number of threads used to service the light client
// requests based on the user-specified value.
threads := config.LightServ * 4 / 100
if threads < 4 {
threads = 4
}
csvLogger := csvlogger.NewLogger(logFileName, time.Second*10, "event, peerId")
reqLogger := csvLogger
if !logRequestServing {
reqLogger = nil
}
srv := &LesServer{
lesCommons: lesCommons{
genesis: e.BlockChain().Genesis().Hash(),
config: config,
chainConfig: e.BlockChain().Config(),
iConfig: light.DefaultServerIndexerConfig,
chainDb: e.ChainDb(),
peers: newPeerSet(),
chainReader: e.BlockChain(),
chtIndexer: light.NewChtIndexer(e.ChainDb(), nil, params.CHTFrequency, params.HelperTrieProcessConfirmations),
bloomTrieIndexer: light.NewBloomTrieIndexer(e.ChainDb(), nil, params.BloomBitsBlocks, params.BloomTrieFrequency),
closeCh: make(chan struct{}),
},
quitSync: quitSync,
lesTopics: lesTopics,
onlyAnnounce: config.OnlyAnnounce,
csvLogger: csvLogger,
logTotalCap: requestLogger.NewChannel("totalCapacity", 0.01),
fcManager: flowcontrol.NewClientManager(nil, &mclock.System{}),
servingQueue: newServingQueue(int64(time.Millisecond*10), float64(config.LightServ)/100, reqLogger),
threadsBusy: config.LightServ/100 + 1,
threadsIdle: threads,
logger: csvLogger,
}
srv.costTracker, srv.minCapacity = newCostTracker(e.ChainDb(), config, requestLogger)
handlerLog := csvLogger
if !logHandlerEvents {
handlerLog = nil
}
srv.handler = newServerHandler(srv, e.BlockChain(), e.ChainDb(), e.TxPool(), handlerLog, e.Synced)
srv.costTracker, srv.minCapacity = newCostTracker(e.ChainDb(), config, reqLogger)
srv.registrar = newCheckpointRegistrar(srv.chainConfig.CheckpointContract, srv.localCheckpoint)
logger := log.New()
srv.thcNormal = config.LightServ * 4 / 100
if srv.thcNormal < 4 {
srv.thcNormal = 4
// Initialize server capacity management fields.
srv.freeClientCap = srv.minCapacity
srv.defParams = flowcontrol.ServerParams{
BufLimit: srv.freeClientCap * bufLimitRatio,
MinRecharge: srv.freeClientCap,
}
srv.thcBlockProcessing = config.LightServ/100 + 1
srv.fcManager = flowcontrol.NewClientManager(nil, &mclock.System{})
// LES flow control tries to more or less guarantee the possibility for the
// clients to send a certain amount of requests at any time and get a quick
// response. Most of the clients want this guarantee but don't actually need
// to send requests most of the time. Our goal is to serve as many clients as
// possible while the actually used server capacity does not exceed the limits
totalRecharge := srv.costTracker.totalRecharge()
maxCapacity := srv.freeClientCap * uint64(srv.config.LightPeers)
if totalRecharge > maxCapacity {
maxCapacity = totalRecharge
}
srv.fcManager.SetCapacityLimits(srv.freeClientCap, maxCapacity, srv.freeClientCap*2)
metricsLogger, eventLogger := csvLogger, csvLogger
if !logClientPoolMetrics {
metricsLogger = nil
}
if !logClientPoolEvents {
eventLogger = nil
}
srv.freeClientPool = newFreeClientPool(srv.chainDb, srv.freeClientCap, 10000, mclock.System{}, func(id string) { go srv.peers.Unregister(id) }, eventLogger, metricsLogger)
srv.priorityClientPool = newPriorityClientPool(srv.freeClientCap, srv.peers, srv.freeClientPool, eventLogger, metricsLogger)
checkpoint := srv.latestLocalCheckpoint()
if !checkpoint.Empty() {
logger.Info("Loaded latest checkpoint", "section", checkpoint.SectionIndex, "head", checkpoint.SectionHead,
log.Info("Loaded latest checkpoint", "section", checkpoint.SectionIndex, "head", checkpoint.SectionHead,
"chtroot", checkpoint.CHTRoot, "bloomroot", checkpoint.BloomRoot)
}
srv.chtIndexer.Start(e.BlockChain())
registrar := newCheckpointRegistrar(e.BlockChain().Config().CheckpointContract, srv.getLocalCheckpoint)
pm, err := NewProtocolManager(e.BlockChain().Config(), light.DefaultServerIndexerConfig, config.ULC, false, config.NetworkId, e.EventMux(), newPeerSet(), e.BlockChain(), e.TxPool(), e.ChainDb(), nil, nil, registrar, quitSync, new(sync.WaitGroup), e.Synced)
if err != nil {
return nil, err
}
srv.protocolManager = pm
if logProtocolHandler {
pm.logger = csvLogger
}
pm.servingQueue = newServingQueue(int64(time.Millisecond*10), float64(config.LightServ)/100, requestLogger)
pm.server = srv
return srv, nil
}
@ -143,113 +163,31 @@ func (s *LesServer) APIs() []rpc.API {
{
Namespace: "les",
Version: "1.0",
Service: NewPrivateLightAPI(&s.lesCommons, s.protocolManager.reg),
Service: NewPrivateLightAPI(&s.lesCommons),
Public: false,
},
}
}
// startEventLoop starts an event handler loop that updates the recharge curve of
// the client manager and adjusts the client pool's size according to the total
// capacity updates coming from the client manager
func (s *LesServer) startEventLoop() {
s.protocolManager.wg.Add(1)
blockProcLogger := s.csvLogger
if !logBlockProcEvents {
blockProcLogger = nil
}
var processing, procLast bool
blockProcFeed := make(chan bool, 100)
s.protocolManager.blockchain.(*core.BlockChain).SubscribeBlockProcessingEvent(blockProcFeed)
totalRechargeCh := make(chan uint64, 100)
totalRecharge := s.costTracker.subscribeTotalRecharge(totalRechargeCh)
totalCapacityCh := make(chan uint64, 100)
updateRecharge := func() {
if processing {
if !procLast {
blockProcLogger.Event("block processing started")
}
s.protocolManager.servingQueue.setThreads(s.thcBlockProcessing)
s.fcManager.SetRechargeCurve(flowcontrol.PieceWiseLinear{{0, 0}, {totalRecharge, totalRecharge}})
} else {
if procLast {
blockProcLogger.Event("block processing finished")
}
s.protocolManager.servingQueue.setThreads(s.thcNormal)
s.fcManager.SetRechargeCurve(flowcontrol.PieceWiseLinear{{0, 0}, {totalRecharge / 16, totalRecharge / 2}, {totalRecharge / 2, totalRecharge / 2}, {totalRecharge, totalRecharge}})
}
procLast = processing
}
updateRecharge()
totalCapacity := s.fcManager.SubscribeTotalCapacity(totalCapacityCh)
s.priorityClientPool.setLimits(s.maxPeers, totalCapacity)
var maxFreePeers uint64
go func() {
for {
select {
case processing = <-blockProcFeed:
updateRecharge()
case totalRecharge = <-totalRechargeCh:
updateRecharge()
case totalCapacity = <-totalCapacityCh:
s.logTotalCap.Update(float64(totalCapacity))
newFreePeers := totalCapacity / s.freeClientCap
if newFreePeers < maxFreePeers && newFreePeers < uint64(s.maxPeers) {
log.Warn("Reduced total capacity", "maxFreePeers", newFreePeers)
}
maxFreePeers = newFreePeers
s.priorityClientPool.setLimits(s.maxPeers, totalCapacity)
case <-s.protocolManager.quitSync:
s.protocolManager.wg.Done()
return
}
}
}()
}
func (s *LesServer) Protocols() []p2p.Protocol {
return s.makeProtocols(ServerProtocolVersions)
return s.makeProtocols(ServerProtocolVersions, s.handler.runPeer, func(id enode.ID) interface{} {
if p := s.peers.Peer(fmt.Sprintf("%x", id.Bytes())); p != nil {
return p.Info()
}
return nil
})
}
// Start starts the LES server
func (s *LesServer) Start(srvr *p2p.Server) {
s.maxPeers = s.config.LightPeers
totalRecharge := s.costTracker.totalRecharge()
if s.maxPeers > 0 {
s.freeClientCap = s.minCapacity //totalRecharge / uint64(s.maxPeers)
if s.freeClientCap < s.minCapacity {
s.freeClientCap = s.minCapacity
}
if s.freeClientCap > 0 {
s.defParams = flowcontrol.ServerParams{
BufLimit: s.freeClientCap * bufLimitRatio,
MinRecharge: s.freeClientCap,
}
}
}
s.privateKey = srvr.PrivateKey
maxCapacity := s.freeClientCap * uint64(s.maxPeers)
if totalRecharge > maxCapacity {
maxCapacity = totalRecharge
}
s.fcManager.SetCapacityLimits(s.freeClientCap, maxCapacity, s.freeClientCap*2)
poolMetricsLogger := s.csvLogger
if !logClientPoolMetrics {
poolMetricsLogger = nil
}
poolEventLogger := s.csvLogger
if !logClientPoolEvents {
poolEventLogger = nil
}
s.freeClientPool = newFreeClientPool(s.chainDb, s.freeClientCap, 10000, mclock.System{}, func(id string) { go s.protocolManager.removePeer(id) }, poolMetricsLogger, poolEventLogger)
s.priorityClientPool = newPriorityClientPool(s.freeClientCap, s.protocolManager.peers, s.freeClientPool, poolMetricsLogger, poolEventLogger)
s.logger.Start()
s.handler.start()
s.wg.Add(1)
go s.capacityManagement()
s.protocolManager.peers.notify(s.priorityClientPool)
s.csvLogger.Start()
s.startEventLoop()
s.protocolManager.Start(s.config.LightPeers)
if srvr.DiscV5 != nil {
for _, topic := range s.lesTopics {
topic := topic
@ -258,12 +196,33 @@ func (s *LesServer) Start(srvr *p2p.Server) {
logger.Info("Starting topic registration")
defer logger.Info("Terminated topic registration")
srvr.DiscV5.RegisterTopic(topic, s.quitSync)
srvr.DiscV5.RegisterTopic(topic, s.closeCh)
}()
}
}
s.privateKey = srvr.PrivateKey
s.protocolManager.blockLoop()
}
// Stop stops the LES service
func (s *LesServer) Stop() {
close(s.closeCh)
// Disconnect existing sessions.
// This also closes the gate for any new registrations on the peer set.
// sessions which are already established but not added to pm.peers yet
// will exit when they try to register.
s.peers.Close()
s.fcManager.Stop()
s.freeClientPool.stop()
s.costTracker.stop()
s.handler.stop()
s.servingQueue.stop()
s.logger.Stop()
// Note, bloom trie indexer is closed by parent bloombits indexer.
s.chtIndexer.Close()
s.wg.Wait()
log.Info("Les server stopped")
}
func (s *LesServer) SetBloomBitsIndexer(bloomIndexer *core.ChainIndexer) {
@ -272,79 +231,71 @@ func (s *LesServer) SetBloomBitsIndexer(bloomIndexer *core.ChainIndexer) {
// SetClient sets the rpc client and starts running checkpoint contract if it is not yet watched.
func (s *LesServer) SetContractBackend(backend bind.ContractBackend) {
if s.protocolManager.reg != nil {
s.protocolManager.reg.start(backend)
if s.registrar != nil {
s.registrar.start(backend)
}
}
// Stop stops the LES service
func (s *LesServer) Stop() {
s.fcManager.Stop()
s.chtIndexer.Close()
// bloom trie indexer is closed by parent bloombits indexer
go func() {
<-s.protocolManager.noMorePeers
}()
s.freeClientPool.stop()
s.costTracker.stop()
s.protocolManager.Stop()
s.csvLogger.Stop()
}
// capacityManagement starts an event handler loop that updates the recharge curve of
// the client manager and adjusts the client pool's size according to the total
// capacity updates coming from the client manager
func (s *LesServer) capacityManagement() {
defer s.wg.Done()
// todo(rjl493456442) separate client and server implementation.
func (pm *ProtocolManager) blockLoop() {
pm.wg.Add(1)
headCh := make(chan core.ChainHeadEvent, 10)
headSub := pm.blockchain.SubscribeChainHeadEvent(headCh)
go func() {
var lastHead *types.Header
lastBroadcastTd := common.Big0
for {
select {
case ev := <-headCh:
peers := pm.peers.AllPeers()
if len(peers) > 0 {
header := ev.Block.Header()
hash := header.Hash()
number := header.Number.Uint64()
td := rawdb.ReadTd(pm.chainDb, hash, number)
if td != nil && td.Cmp(lastBroadcastTd) > 0 {
var reorg uint64
if lastHead != nil {
reorg = lastHead.Number.Uint64() - rawdb.FindCommonAncestor(pm.chainDb, header, lastHead).Number.Uint64()
}
lastHead = header
lastBroadcastTd = td
processCh := make(chan bool, 100)
sub := s.handler.blockchain.SubscribeBlockProcessingEvent(processCh)
defer sub.Unsubscribe()
log.Debug("Announcing block to peers", "number", number, "hash", hash, "td", td, "reorg", reorg)
totalRechargeCh := make(chan uint64, 100)
totalRecharge := s.costTracker.subscribeTotalRecharge(totalRechargeCh)
announce := announceData{Hash: hash, Number: number, Td: td, ReorgDepth: reorg}
var (
signed bool
signedAnnounce announceData
)
totalCapacityCh := make(chan uint64, 100)
totalCapacity := s.fcManager.SubscribeTotalCapacity(totalCapacityCh)
s.priorityClientPool.setLimits(s.config.LightPeers, totalCapacity)
for _, p := range peers {
p := p
switch p.announceType {
case announceTypeSimple:
p.queueSend(func() { p.SendAnnounce(announce) })
case announceTypeSigned:
if !signed {
signedAnnounce = announce
signedAnnounce.sign(pm.server.privateKey)
signed = true
}
p.queueSend(func() { p.SendAnnounce(signedAnnounce) })
}
}
}
}
case <-pm.quitSync:
headSub.Unsubscribe()
pm.wg.Done()
return
var (
busy bool
freePeers uint64
)
updateRecharge := func() {
if busy {
if logBlockProcEvents {
s.logger.Event("block processing started")
}
s.servingQueue.setThreads(s.threadsBusy)
s.fcManager.SetRechargeCurve(flowcontrol.PieceWiseLinear{{0, 0}, {totalRecharge, totalRecharge}})
} else {
if logBlockProcEvents {
s.logger.Event("block processing finished")
}
s.servingQueue.setThreads(s.threadsIdle)
s.fcManager.SetRechargeCurve(flowcontrol.PieceWiseLinear{{0, 0}, {totalRecharge / 10, totalRecharge}, {totalRecharge, totalRecharge}})
}
}()
}
updateRecharge()
// Record the change of total capacity if required.
var totalCapChannel *csvlogger.Channel
if logRequestServing {
totalCapChannel = s.logger.NewChannel("totalCapacity", 0.01)
}
for {
select {
case busy = <-processCh:
updateRecharge()
case totalRecharge = <-totalRechargeCh:
updateRecharge()
case totalCapacity = <-totalCapacityCh:
totalCapChannel.Update(float64(totalCapacity))
newFreePeers := totalCapacity / s.freeClientCap
if newFreePeers < freePeers && newFreePeers < uint64(s.config.LightPeers) {
log.Warn("Reduced free peer connections", "from", freePeers, "to", newFreePeers)
}
freePeers = newFreePeers
s.priorityClientPool.setLimits(s.config.LightPeers, totalCapacity)
case <-s.closeCh:
return
}
}
}

792
les/server_handler.go Normal file
View file

@ -0,0 +1,792 @@
// Copyright 2019 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 <http://www.gnu.org/licenses/>.
package les
import (
"encoding/binary"
"encoding/json"
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/les/csvlogger"
"github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
)
const (
softResponseLimit = 2 * 1024 * 1024 // Target maximum size of returned blocks, headers or node data.
estHeaderRlpSize = 500 // Approximate size of an RLP encoded block header
ethVersion = 63 // equivalent eth version for the downloader
MaxHeaderFetch = 192 // Amount of block headers to be fetched per retrieval request
MaxBodyFetch = 32 // Amount of block bodies to be fetched per retrieval request
MaxReceiptFetch = 128 // Amount of transaction receipts to allow fetching per request
MaxCodeFetch = 64 // Amount of contract codes to allow fetching per request
MaxProofsFetch = 64 // Amount of merkle proofs to be fetched per retrieval request
MaxHelperTrieProofsFetch = 64 // Amount of merkle proofs to be fetched per retrieval request
MaxTxSend = 64 // Amount of transactions to be send per request
MaxTxStatus = 256 // Amount of transactions to queried per request
)
// serverHandler is responsible for serving light client and process
// all incoming light requests.
type serverHandler struct {
blockchain *core.BlockChain
chainDb ethdb.Database
txpool *core.TxPool
server *LesServer
logger *csvlogger.Logger
closeCh chan struct{} // Channel used to exit all background routines of handler.
wg sync.WaitGroup // WaitGroup used to track all background routines of handler.
synced func() bool // Callback function used to determine whether local node is synced.
}
func newServerHandler(server *LesServer, blockchain *core.BlockChain, chainDb ethdb.Database, txpool *core.TxPool, logger *csvlogger.Logger, synced func() bool) *serverHandler {
handler := &serverHandler{
server: server,
blockchain: blockchain,
chainDb: chainDb,
txpool: txpool,
logger: logger,
closeCh: make(chan struct{}),
synced: synced,
}
return handler
}
// start starts the server handler.
func (h *serverHandler) start() {
h.wg.Add(1)
go h.broadcastHeaders()
}
// stop stops the server handler.
func (h *serverHandler) stop() {
close(h.closeCh)
h.wg.Wait()
}
// runPeer is the p2p protocol run function for the given version.
func (h *serverHandler) runPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) error {
peer := newPeer(int(version), h.server.config.NetworkId, false, p, rw)
h.wg.Add(1)
defer h.wg.Done()
return h.handle(peer)
}
func (h *serverHandler) handle(p *peer) error {
// Reject light clients if server is not synced.
if !h.synced() {
h.logger.Event("Rejected (server is not synced), " + p.id)
return p2p.DiscRequested
}
p.Log().Debug("Light Ethereum peer connected", "name", p.Name())
// Execute the LES handshake
var (
head = h.blockchain.CurrentHeader()
hash = head.Hash()
number = head.Number.Uint64()
td = h.blockchain.GetTd(hash, number)
)
if err := p.Handshake(td, hash, number, h.blockchain.Genesis().Hash(), h.server); err != nil {
h.logger.Event("Handshake error: " + err.Error() + ", " + p.id)
p.Log().Debug("Light Ethereum handshake failed", "err", err)
return err
}
defer p.fcClient.Disconnect()
if rw, ok := p.rw.(*meteredMsgReadWriter); ok {
rw.Init(p.version)
}
// Register the peer locally
if err := h.server.peers.Register(p); err != nil {
h.logger.Event("Peer registration error: " + err.Error() + ", " + p.id)
p.Log().Error("Light Ethereum peer registration failed", "err", err)
return err
}
defer h.logger.Event("Closed connection, " + p.id)
defer h.server.peers.Unregister(p.id)
// Spawn a main loop to handle all incoming messages.
for {
select {
case err := <-p.errCh:
h.logger.Event("Failed to send response: " + err.Error() + ", " + p.id)
p.Log().Debug("Failed to send light ethereum response", "err", err)
return err
default:
}
if err := h.handleMsg(p); err != nil {
h.logger.Event("Message handling error: " + err.Error() + ", " + p.id)
p.Log().Debug("Light Ethereum message handling failed", "err", err)
return err
}
}
}
// handleMsg is invoked whenever an inbound message is received from a remote
// peer. The remote connection is torn down upon returning any error.
func (h *serverHandler) handleMsg(p *peer) error {
// Read the next message from the remote peer, and ensure it's fully consumed
msg, err := p.rw.ReadMsg()
if err != nil {
return err
}
p.Log().Trace("Light Ethereum message arrived", "code", msg.Code, "bytes", msg.Size)
// Discard large message which exceeds the limitation.
if msg.Size > ProtocolMaxMsgSize {
return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, ProtocolMaxMsgSize)
}
defer msg.Discard()
var (
maxCost uint64
task *servingTask
respId = p.responseID()
)
// accept returns an indicator whether the request can be served.
// If so, deduct the max cost from the flow control buffer.
accept := func(reqID, reqCnt, maxCnt uint64) bool {
// Short circuit if the peer is already frozen or the request number is invalid.
inSizeCost := h.server.costTracker.realCost(0, msg.Size, 0)
if p.isFrozen() || reqCnt == 0 || reqCnt > maxCnt {
p.fcClient.OneTimeCost(inSizeCost)
return false
}
// Prepaid max cost units before request been serving.
maxCost = p.fcCosts.getMaxCost(msg.Code, reqCnt)
accepted, bufShort, priority := p.fcClient.AcceptRequest(reqID, respId, maxCost)
if !accepted {
p.freezeClient()
p.Log().Error("Request came too early", "remaining", common.PrettyDuration(time.Duration(bufShort*1000000/p.fcParams.MinRecharge)))
p.fcClient.OneTimeCost(inSizeCost)
return false
}
// Create a multi-stage task, estimate the time it takes for the task to
// execute, and cache it in the request service queue.
factor := h.server.costTracker.globalFactor()
if factor < 0.001 {
factor = 1
}
maxTime := uint64(float64(maxCost) / factor)
task = h.server.servingQueue.newTask(p, maxTime, priority)
if task.start() {
return true
}
p.fcClient.RequestProcessed(reqID, respId, maxCost, inSizeCost)
return false
}
// sendResponse sends back the response and updates the flow control statistic.
sendResponse := func(reqID, amount uint64, reply *reply, servingTime uint64) {
// Short circuit if the client is already frozen.
if p.isFrozen() {
realCost := h.server.costTracker.realCost(servingTime, msg.Size, 0)
p.fcClient.RequestProcessed(reqID, respId, maxCost, realCost)
return
}
// Positive correction buffer value with real cost.
var replySize uint32
if reply != nil {
replySize = reply.size()
}
realCost := h.server.costTracker.realCost(servingTime, msg.Size, replySize)
if h.server.costTracker.disableRealCost {
realCost = maxCost
}
bv := p.fcClient.RequestProcessed(reqID, respId, maxCost, realCost)
// Feed cost tracker request serving statistic.
if amount != 0 {
h.server.costTracker.updateStats(msg.Code, amount, servingTime, realCost)
}
if reply != nil {
p.queueSend(func() {
if err := reply.send(bv); err != nil {
select {
case p.errCh <- err:
default:
}
}
})
}
}
switch msg.Code {
case GetBlockHeadersMsg:
p.Log().Trace("Received block header request")
var req struct {
ReqID uint64
Query getBlockHeadersData
}
if err := msg.Decode(&req); err != nil {
return errResp(ErrDecode, "%v: %v", msg, err)
}
query := req.Query
if accept(req.ReqID, query.Amount, MaxHeaderFetch) {
go func() {
hashMode := query.Origin.Hash != (common.Hash{})
first := true
maxNonCanonical := uint64(100)
// Gather headers until the fetch or network limits is reached
var (
bytes common.StorageSize
headers []*types.Header
unknown bool
)
for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit {
if !first && !task.waitOrStop() {
sendResponse(req.ReqID, 0, nil, task.servingTime)
return
}
// Retrieve the next header satisfying the query
var origin *types.Header
if hashMode {
if first {
origin = h.blockchain.GetHeaderByHash(query.Origin.Hash)
if origin != nil {
query.Origin.Number = origin.Number.Uint64()
}
} else {
origin = h.blockchain.GetHeader(query.Origin.Hash, query.Origin.Number)
}
} else {
origin = h.blockchain.GetHeaderByNumber(query.Origin.Number)
}
if origin == nil {
break
}
headers = append(headers, origin)
bytes += estHeaderRlpSize
// Advance to the next header of the query
switch {
case hashMode && query.Reverse:
// Hash based traversal towards the genesis block
ancestor := query.Skip + 1
if ancestor == 0 {
unknown = true
} else {
query.Origin.Hash, query.Origin.Number = h.blockchain.GetAncestor(query.Origin.Hash, query.Origin.Number, ancestor, &maxNonCanonical)
unknown = query.Origin.Hash == common.Hash{}
}
case hashMode && !query.Reverse:
// Hash based traversal towards the leaf block
var (
current = origin.Number.Uint64()
next = current + query.Skip + 1
)
if next <= current {
infos, _ := json.MarshalIndent(p.Peer.Info(), "", " ")
p.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", query.Skip, "next", next, "attacker", infos)
unknown = true
} else {
if header := h.blockchain.GetHeaderByNumber(next); header != nil {
nextHash := header.Hash()
expOldHash, _ := h.blockchain.GetAncestor(nextHash, next, query.Skip+1, &maxNonCanonical)
if expOldHash == query.Origin.Hash {
query.Origin.Hash, query.Origin.Number = nextHash, next
} else {
unknown = true
}
} else {
unknown = true
}
}
case query.Reverse:
// Number based traversal towards the genesis block
if query.Origin.Number >= query.Skip+1 {
query.Origin.Number -= query.Skip + 1
} else {
unknown = true
}
case !query.Reverse:
// Number based traversal towards the leaf block
query.Origin.Number += query.Skip + 1
}
first = false
}
sendResponse(req.ReqID, query.Amount, p.ReplyBlockHeaders(req.ReqID, headers), task.done())
}()
}
case GetBlockBodiesMsg:
p.Log().Trace("Received block bodies request")
var req struct {
ReqID uint64
Hashes []common.Hash
}
if err := msg.Decode(&req); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
var (
bytes int
bodies []rlp.RawValue
)
reqCnt := len(req.Hashes)
if accept(req.ReqID, uint64(reqCnt), MaxBodyFetch) {
go func() {
for i, hash := range req.Hashes {
if i != 0 && !task.waitOrStop() {
sendResponse(req.ReqID, 0, nil, task.servingTime)
return
}
if bytes >= softResponseLimit {
break
}
// Retrieve the requested block body, stopping if enough was found
if number := rawdb.ReadHeaderNumber(h.chainDb, hash); number != nil {
if data := rawdb.ReadBodyRLP(h.chainDb, hash, *number); len(data) != 0 {
bodies = append(bodies, data)
bytes += len(data)
}
}
}
sendResponse(req.ReqID, uint64(reqCnt), p.ReplyBlockBodiesRLP(req.ReqID, bodies), task.done())
}()
}
case GetCodeMsg:
p.Log().Trace("Received code request")
var req struct {
ReqID uint64
Reqs []CodeReq
}
if err := msg.Decode(&req); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
var (
bytes int
data [][]byte
)
reqCnt := len(req.Reqs)
if accept(req.ReqID, uint64(reqCnt), MaxCodeFetch) {
go func() {
for i, request := range req.Reqs {
if i != 0 && !task.waitOrStop() {
sendResponse(req.ReqID, 0, nil, task.servingTime)
return
}
// Look up the root hash belonging to the request
number := rawdb.ReadHeaderNumber(h.chainDb, request.BHash)
if number == nil {
p.Log().Warn("Failed to retrieve block num for code", "hash", request.BHash)
continue
}
header := rawdb.ReadHeader(h.chainDb, request.BHash, *number)
if header == nil {
p.Log().Warn("Failed to retrieve header for code", "block", *number, "hash", request.BHash)
continue
}
triedb := h.blockchain.StateCache().TrieDB()
account, err := h.getAccount(triedb, header.Root, common.BytesToHash(request.AccKey))
if err != nil {
p.Log().Warn("Failed to retrieve account for code", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "err", err)
continue
}
code, err := triedb.Node(common.BytesToHash(account.CodeHash))
if err != nil {
p.Log().Warn("Failed to retrieve account code", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "codehash", common.BytesToHash(account.CodeHash), "err", err)
continue
}
// Accumulate the code and abort if enough data was retrieved
data = append(data, code)
if bytes += len(code); bytes >= softResponseLimit {
break
}
}
sendResponse(req.ReqID, uint64(reqCnt), p.ReplyCode(req.ReqID, data), task.done())
}()
}
case GetReceiptsMsg:
p.Log().Trace("Received receipts request")
var req struct {
ReqID uint64
Hashes []common.Hash
}
if err := msg.Decode(&req); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
var (
bytes int
receipts []rlp.RawValue
)
reqCnt := len(req.Hashes)
if accept(req.ReqID, uint64(reqCnt), MaxReceiptFetch) {
go func() {
for i, hash := range req.Hashes {
if i != 0 && !task.waitOrStop() {
sendResponse(req.ReqID, 0, nil, task.servingTime)
return
}
if bytes >= softResponseLimit {
break
}
// Retrieve the requested block's receipts, skipping if unknown to us
var results types.Receipts
if number := rawdb.ReadHeaderNumber(h.chainDb, hash); number != nil {
results = rawdb.ReadRawReceipts(h.chainDb, hash, *number)
}
if results == nil {
if header := h.blockchain.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash {
continue
}
}
// If known, encode and queue for response packet
if encoded, err := rlp.EncodeToBytes(results); err != nil {
log.Error("Failed to encode receipt", "err", err)
} else {
receipts = append(receipts, encoded)
bytes += len(encoded)
}
}
sendResponse(req.ReqID, uint64(reqCnt), p.ReplyReceiptsRLP(req.ReqID, receipts), task.done())
}()
}
case GetProofsV2Msg:
p.Log().Trace("Received les/2 proofs request")
var req struct {
ReqID uint64
Reqs []ProofReq
}
if err := msg.Decode(&req); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
// Gather state data until the fetch or network limits is reached
var (
lastBHash common.Hash
root common.Hash
)
reqCnt := len(req.Reqs)
if accept(req.ReqID, uint64(reqCnt), MaxProofsFetch) {
go func() {
nodes := light.NewNodeSet()
for i, request := range req.Reqs {
if i != 0 && !task.waitOrStop() {
sendResponse(req.ReqID, 0, nil, task.servingTime)
return
}
// Look up the root hash belonging to the request
var (
number *uint64
header *types.Header
trie state.Trie
)
if request.BHash != lastBHash {
root, lastBHash = common.Hash{}, request.BHash
if number = rawdb.ReadHeaderNumber(h.chainDb, request.BHash); number == nil {
p.Log().Warn("Failed to retrieve block num for proof", "hash", request.BHash)
continue
}
if header = rawdb.ReadHeader(h.chainDb, request.BHash, *number); header == nil {
p.Log().Warn("Failed to retrieve header for proof", "block", *number, "hash", request.BHash)
continue
}
root = header.Root
}
// Open the account or storage trie for the request
statedb := h.blockchain.StateCache()
switch len(request.AccKey) {
case 0:
// No account key specified, open an account trie
trie, err = statedb.OpenTrie(root)
if trie == nil || err != nil {
p.Log().Warn("Failed to open storage trie for proof", "block", header.Number, "hash", header.Hash(), "root", root, "err", err)
continue
}
default:
// Account key specified, open a storage trie
account, err := h.getAccount(statedb.TrieDB(), root, common.BytesToHash(request.AccKey))
if err != nil {
p.Log().Warn("Failed to retrieve account for proof", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "err", err)
continue
}
trie, err = statedb.OpenStorageTrie(common.BytesToHash(request.AccKey), account.Root)
if trie == nil || err != nil {
p.Log().Warn("Failed to open storage trie for proof", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "root", account.Root, "err", err)
continue
}
}
// Prove the user's request from the account or stroage trie
if err := trie.Prove(request.Key, request.FromLevel, nodes); err != nil {
p.Log().Warn("Failed to prove state request", "block", header.Number, "hash", header.Hash(), "err", err)
continue
}
if nodes.DataSize() >= softResponseLimit {
break
}
}
sendResponse(req.ReqID, uint64(reqCnt), p.ReplyProofsV2(req.ReqID, nodes.NodeList()), task.done())
}()
}
case GetHelperTrieProofsMsg:
p.Log().Trace("Received helper trie proof request")
var req struct {
ReqID uint64
Reqs []HelperTrieReq
}
if err := msg.Decode(&req); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
// Gather state data until the fetch or network limits is reached
var (
auxBytes int
auxData [][]byte
)
reqCnt := len(req.Reqs)
if accept(req.ReqID, uint64(reqCnt), MaxHelperTrieProofsFetch) {
go func() {
var (
lastIdx uint64
lastType uint
root common.Hash
auxTrie *trie.Trie
)
nodes := light.NewNodeSet()
for i, request := range req.Reqs {
if i != 0 && !task.waitOrStop() {
sendResponse(req.ReqID, 0, nil, task.servingTime)
return
}
if auxTrie == nil || request.Type != lastType || request.TrieIdx != lastIdx {
auxTrie, lastType, lastIdx = nil, request.Type, request.TrieIdx
var prefix string
if root, prefix = h.getHelperTrie(request.Type, request.TrieIdx); root != (common.Hash{}) {
auxTrie, _ = trie.New(root, trie.NewDatabase(rawdb.NewTable(h.chainDb, prefix)))
}
}
if request.AuxReq == auxRoot {
var data []byte
if root != (common.Hash{}) {
data = root[:]
}
auxData = append(auxData, data)
auxBytes += len(data)
} else {
if auxTrie != nil {
auxTrie.Prove(request.Key, request.FromLevel, nodes)
}
if request.AuxReq != 0 {
data := h.getAuxiliaryHeaders(request)
auxData = append(auxData, data)
auxBytes += len(data)
}
}
if nodes.DataSize()+auxBytes >= softResponseLimit {
break
}
}
sendResponse(req.ReqID, uint64(reqCnt), p.ReplyHelperTrieProofs(req.ReqID, HelperTrieResps{Proofs: nodes.NodeList(), AuxData: auxData}), task.done())
}()
}
case SendTxV2Msg:
p.Log().Trace("Received new transactions")
var req struct {
ReqID uint64
Txs []*types.Transaction
}
if err := msg.Decode(&req); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
reqCnt := len(req.Txs)
if accept(req.ReqID, uint64(reqCnt), MaxTxSend) {
go func() {
stats := make([]light.TxStatus, len(req.Txs))
for i, tx := range req.Txs {
if i != 0 && !task.waitOrStop() {
return
}
hash := tx.Hash()
stats[i] = h.txStatus(hash)
if stats[i].Status == core.TxStatusUnknown {
if errs := h.txpool.AddRemotes([]*types.Transaction{tx}); errs[0] != nil {
stats[i].Error = errs[0].Error()
continue
}
stats[i] = h.txStatus(hash)
}
}
sendResponse(req.ReqID, uint64(reqCnt), p.ReplyTxStatus(req.ReqID, stats), task.done())
}()
}
case GetTxStatusMsg:
p.Log().Trace("Received transaction status query request")
var req struct {
ReqID uint64
Hashes []common.Hash
}
if err := msg.Decode(&req); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
reqCnt := len(req.Hashes)
if accept(req.ReqID, uint64(reqCnt), MaxTxStatus) {
go func() {
stats := make([]light.TxStatus, len(req.Hashes))
for i, hash := range req.Hashes {
if i != 0 && !task.waitOrStop() {
sendResponse(req.ReqID, 0, nil, task.servingTime)
return
}
stats[i] = h.txStatus(hash)
}
sendResponse(req.ReqID, uint64(reqCnt), p.ReplyTxStatus(req.ReqID, stats), task.done())
}()
}
default:
p.Log().Trace("Received invalid message", "code", msg.Code)
return errResp(ErrInvalidMsgCode, "%v", msg.Code)
}
return nil
}
// getAccount retrieves an account from the state based on root.
func (h *serverHandler) getAccount(triedb *trie.Database, root, hash common.Hash) (state.Account, error) {
trie, err := trie.New(root, triedb)
if err != nil {
return state.Account{}, err
}
blob, err := trie.TryGet(hash[:])
if err != nil {
return state.Account{}, err
}
var account state.Account
if err = rlp.DecodeBytes(blob, &account); err != nil {
return state.Account{}, err
}
return account, nil
}
// getHelperTrie returns the post-processed trie root for the given trie ID and section index
func (h *serverHandler) getHelperTrie(typ uint, index uint64) (common.Hash, string) {
switch typ {
case htCanonical:
sectionHead := rawdb.ReadCanonicalHash(h.chainDb, (index+1)*h.server.iConfig.ChtSize-1)
return light.GetChtRoot(h.chainDb, index, sectionHead), light.ChtTablePrefix
case htBloomBits:
sectionHead := rawdb.ReadCanonicalHash(h.chainDb, (index+1)*h.server.iConfig.BloomTrieSize-1)
return light.GetBloomTrieRoot(h.chainDb, index, sectionHead), light.BloomTrieTablePrefix
}
return common.Hash{}, ""
}
// getAuxiliaryHeaders returns requested auxiliary headers for the CHT request.
func (h *serverHandler) getAuxiliaryHeaders(req HelperTrieReq) []byte {
if req.Type == htCanonical && req.AuxReq == auxHeader && len(req.Key) == 8 {
blockNum := binary.BigEndian.Uint64(req.Key)
hash := rawdb.ReadCanonicalHash(h.chainDb, blockNum)
return rawdb.ReadHeaderRLP(h.chainDb, hash, blockNum)
}
return nil
}
// txStatus returns the status of a specified transaction.
func (h *serverHandler) txStatus(hash common.Hash) light.TxStatus {
var stat light.TxStatus
// Looking the transaction in txpool first.
stat.Status = h.txpool.Status([]common.Hash{hash})[0]
// If the transaction is unknown to the pool, try looking it up locally.
if stat.Status == core.TxStatusUnknown {
if tx, blockHash, blockNumber, txIndex := rawdb.ReadTransaction(h.chainDb, hash); tx != nil {
stat.Status = core.TxStatusIncluded
stat.Lookup = &rawdb.LegacyTxLookupEntry{BlockHash: blockHash, BlockIndex: blockNumber, Index: txIndex}
}
}
return stat
}
// broadcastHeaders broadcasts new block information to all connected light
// clients. According to the agreement between client and server, server should
// only broadcast new announcement if the total difficulty is higher than the
// last one. Besides server will add the signature if client requires.
func (h *serverHandler) broadcastHeaders() {
defer h.wg.Done()
headCh := make(chan core.ChainHeadEvent, 10)
headSub := h.blockchain.SubscribeChainHeadEvent(headCh)
defer headSub.Unsubscribe()
var (
lastHead *types.Header
lastBroadcastTd = common.Big0
)
for {
select {
case ev := <-headCh:
peers := h.server.peers.AllPeers()
if len(peers) == 0 {
continue
}
header := ev.Block.Header()
hash, number := header.Hash(), header.Number.Uint64()
td := rawdb.ReadTd(h.chainDb, hash, number)
if td == nil || td.Cmp(lastBroadcastTd) <= 0 {
continue
}
var reorg uint64
if lastHead != nil {
reorg = lastHead.Number.Uint64() - rawdb.FindCommonAncestor(h.chainDb, header, lastHead).Number.Uint64()
}
lastHead, lastBroadcastTd = header, td
log.Debug("Announcing block to peers", "number", number, "hash", hash, "td", td, "reorg", reorg)
var (
signed bool
signedAnnounce announceData
)
announce := announceData{Hash: hash, Number: number, Td: td, ReorgDepth: reorg}
for _, p := range peers {
p := p
switch p.announceType {
case announceTypeSimple:
p.queueSend(func() { p.SendAnnounce(announce) })
case announceTypeSigned:
if !signed {
signedAnnounce = announce
signedAnnounce.sign(h.server.privateKey)
signed = true
}
p.queueSend(func() { p.SendAnnounce(signedAnnounce) })
}
}
case <-h.closeCh:
return
}
}
}

View file

@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p"
@ -115,8 +116,6 @@ type serverPool struct {
db ethdb.Database
dbKey []byte
server *p2p.Server
quit chan struct{}
wg *sync.WaitGroup
connWg sync.WaitGroup
topic discv5.Topic
@ -137,14 +136,19 @@ type serverPool struct {
connCh chan *connReq
disconnCh chan *disconnReq
registerCh chan *registerReq
closeCh chan struct{}
wg sync.WaitGroup
}
// newServerPool creates a new serverPool instance
func newServerPool(db ethdb.Database, quit chan struct{}, wg *sync.WaitGroup, trustedNodes []string) *serverPool {
func newServerPool(db ethdb.Database, ulcConfig *eth.ULCConfig) *serverPool {
var trustedNodes []string
if ulcConfig != nil {
trustedNodes = ulcConfig.TrustedServers
}
pool := &serverPool{
db: db,
quit: quit,
wg: wg,
entries: make(map[enode.ID]*poolEntry),
timeout: make(chan *poolEntry, 1),
adjustStats: make(chan poolStatAdjust, 100),
@ -152,6 +156,7 @@ func newServerPool(db ethdb.Database, quit chan struct{}, wg *sync.WaitGroup, tr
connCh: make(chan *connReq),
disconnCh: make(chan *disconnReq),
registerCh: make(chan *registerReq),
closeCh: make(chan struct{}),
knownSelect: newWeightedRandomSelect(),
newSelect: newWeightedRandomSelect(),
fastDiscover: true,
@ -167,7 +172,6 @@ func (pool *serverPool) start(server *p2p.Server, topic discv5.Topic) {
pool.server = server
pool.topic = topic
pool.dbKey = append([]byte("serverPool/"), []byte(topic)...)
pool.wg.Add(1)
pool.loadNodes()
pool.connectToTrustedNodes()
@ -178,9 +182,15 @@ func (pool *serverPool) start(server *p2p.Server, topic discv5.Topic) {
go pool.discoverNodes()
}
pool.checkDial()
pool.wg.Add(1)
go pool.eventLoop()
}
func (pool *serverPool) stop() {
close(pool.closeCh)
pool.wg.Wait()
}
// discoverNodes wraps SearchTopic, converting result nodes to enode.Node.
func (pool *serverPool) discoverNodes() {
ch := make(chan *discv5.Node)
@ -207,7 +217,7 @@ func (pool *serverPool) connect(p *peer, node *enode.Node) *poolEntry {
req := &connReq{p: p, node: node, result: make(chan *poolEntry, 1)}
select {
case pool.connCh <- req:
case <-pool.quit:
case <-pool.closeCh:
return nil
}
return <-req.result
@ -219,7 +229,7 @@ func (pool *serverPool) registered(entry *poolEntry) {
req := &registerReq{entry: entry, done: make(chan struct{})}
select {
case pool.registerCh <- req:
case <-pool.quit:
case <-pool.closeCh:
return
}
<-req.done
@ -231,7 +241,7 @@ func (pool *serverPool) registered(entry *poolEntry) {
func (pool *serverPool) disconnect(entry *poolEntry) {
stopped := false
select {
case <-pool.quit:
case <-pool.closeCh:
stopped = true
default:
}
@ -278,6 +288,7 @@ func (pool *serverPool) adjustResponseTime(entry *poolEntry, time time.Duration,
// eventLoop handles pool events and mutex locking for all internal functions
func (pool *serverPool) eventLoop() {
defer pool.wg.Done()
lookupCnt := 0
var convTime mclock.AbsTime
if pool.discSetPeriod != nil {
@ -361,7 +372,7 @@ func (pool *serverPool) eventLoop() {
case req := <-pool.connCh:
if pool.trustedNodes[req.p.ID()] != nil {
// ignore trusted nodes
req.result <- nil
req.result <- &poolEntry{trusted: true}
} else {
// Handle peer connection requests.
entry := pool.entries[req.p.ID()]
@ -389,6 +400,9 @@ func (pool *serverPool) eventLoop() {
}
case req := <-pool.registerCh:
if req.entry.trusted {
continue
}
// Handle peer registration requests.
entry := req.entry
entry.state = psRegistered
@ -402,10 +416,13 @@ func (pool *serverPool) eventLoop() {
close(req.done)
case req := <-pool.disconnCh:
if req.entry.trusted {
continue
}
// Handle peer disconnection requests.
disconnect(req, req.stopped)
case <-pool.quit:
case <-pool.closeCh:
if pool.discSetPeriod != nil {
close(pool.discSetPeriod)
}
@ -421,7 +438,6 @@ func (pool *serverPool) eventLoop() {
disconnect(req, true)
}
pool.saveNodes()
pool.wg.Done()
return
}
}
@ -549,10 +565,10 @@ func (pool *serverPool) setRetryDial(entry *poolEntry) {
entry.delayedRetry = true
go func() {
select {
case <-pool.quit:
case <-pool.closeCh:
case <-time.After(delay):
select {
case <-pool.quit:
case <-pool.closeCh:
case pool.enableRetry <- entry:
}
}
@ -618,10 +634,10 @@ func (pool *serverPool) dial(entry *poolEntry, knownSelected bool) {
go func() {
pool.server.AddPeer(entry.node)
select {
case <-pool.quit:
case <-pool.closeCh:
case <-time.After(dialTimeout):
select {
case <-pool.quit:
case <-pool.closeCh:
case pool.timeout <- entry:
}
}
@ -662,14 +678,14 @@ type poolEntry struct {
lastConnected, dialed *poolEntryAddress
addrSelect weightedRandomSelect
lastDiscovered mclock.AbsTime
known, knownSelected bool
connectStats, delayStats poolStats
responseStats, timeoutStats poolStats
state int
regTime mclock.AbsTime
queueIdx int
removed bool
lastDiscovered mclock.AbsTime
known, knownSelected, trusted bool
connectStats, delayStats poolStats
responseStats, timeoutStats poolStats
state int
regTime mclock.AbsTime
queueIdx int
removed bool
delayedRetry bool
shortRetry int

View file

@ -44,35 +44,6 @@ const (
checkpointSync
)
// syncer is responsible for periodically synchronising with the network, both
// downloading hashes and blocks as well as handling the announcement handler.
func (pm *ProtocolManager) syncer() {
// Start and ensure cleanup of sync mechanisms
//pm.fetcher.Start()
//defer pm.fetcher.Stop()
defer pm.downloader.Terminate()
// Wait for different events to fire synchronisation operations
//forceSync := time.Tick(forceSyncCycle)
for {
select {
case <-pm.newPeerCh:
/* // Make sure we have peers to select from, then sync
if pm.peers.Len() < minDesiredPeerCount {
break
}
go pm.synchronise(pm.peers.BestPeer())
*/
/*case <-forceSync:
// Force a sync even if not enough peers are present
go pm.synchronise(pm.peers.BestPeer())
*/
case <-pm.noMorePeers:
return
}
}
}
// validateCheckpoint verifies the advertised checkpoint by peer is valid or not.
//
// Each network has several hard-coded checkpoint signer addresses. Only the
@ -81,22 +52,22 @@ func (pm *ProtocolManager) syncer() {
// In addition to the checkpoint registered in the registrar contract, there are
// several legacy hardcoded checkpoints in our codebase. These checkpoints are
// also considered as valid.
func (pm *ProtocolManager) validateCheckpoint(peer *peer) error {
func (h *clientHandler) validateCheckpoint(peer *peer) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
// Fetch the block header corresponding to the checkpoint registration.
cp := peer.advertisedCheckpoint
header, err := light.GetUntrustedHeaderByNumber(ctx, pm.odr, peer.registeredHeight, peer.id)
header, err := light.GetUntrustedHeaderByNumber(ctx, h.backend.odr, peer.registeredHeight, peer.id)
if err != nil {
return err
}
// Fetch block logs associated with the block header.
logs, err := light.GetUntrustedBlockLogs(ctx, pm.odr, header)
logs, err := light.GetUntrustedBlockLogs(ctx, h.backend.odr, header)
if err != nil {
return err
}
events := pm.reg.contract.LookupCheckpointEvent(logs, cp.SectionIndex, cp.Hash())
events := h.backend.registrar.contract.LookupCheckpointEvent(logs, cp.SectionIndex, cp.Hash())
if len(events) == 0 {
return errInvalidCheckpoint
}
@ -108,7 +79,7 @@ func (pm *ProtocolManager) validateCheckpoint(peer *peer) error {
for _, event := range events {
signatures = append(signatures, append(event.R[:], append(event.S[:], event.V)...))
}
valid, signers := pm.reg.verifySigner(index, hash, signatures)
valid, signers := h.backend.registrar.verifySigner(index, hash, signatures)
if !valid {
return errInvalidCheckpoint
}
@ -117,14 +88,14 @@ func (pm *ProtocolManager) validateCheckpoint(peer *peer) error {
}
// synchronise tries to sync up our local chain with a remote peer.
func (pm *ProtocolManager) synchronise(peer *peer) {
func (h *clientHandler) synchronise(peer *peer) {
// Short circuit if the peer is nil.
if peer == nil {
return
}
// Make sure the peer's TD is higher than our own.
latest := pm.blockchain.CurrentHeader()
currentTd := rawdb.ReadTd(pm.chainDb, latest.Hash(), latest.Number.Uint64())
latest := h.backend.blockchain.CurrentHeader()
currentTd := rawdb.ReadTd(h.backend.chainDb, latest.Hash(), latest.Number.Uint64())
if currentTd != nil && peer.headBlockInfo().Td.Cmp(currentTd) < 0 {
return
}
@ -142,34 +113,34 @@ func (pm *ProtocolManager) synchronise(peer *peer) {
case cp.Empty():
mode = lightSync
log.Debug("Disable checkpoint syncing", "reason", "empty checkpoint")
case latest.Number.Uint64() >= (cp.SectionIndex+1)*pm.iConfig.ChtSize-1:
case latest.Number.Uint64() >= (cp.SectionIndex+1)*h.backend.iConfig.ChtSize-1:
mode = lightSync
log.Debug("Disable checkpoint syncing", "reason", "local chain beyonds the checkpoint")
case peer.isHardcode:
mode = legacyCheckpointSync
log.Debug("Disable checkpoint syncing", "reason", "checkpoint is hardcoded")
case pm.reg == nil || !pm.reg.isRunning():
case h.backend.registrar == nil || !h.backend.registrar.isRunning():
mode = legacyCheckpointSync
log.Debug("Disable checkpoint syncing", "reason", "checkpoint syncing is not activated")
}
// Notify testing framework if syncing has completed(for testing purpose).
defer func() {
if pm.reg != nil && pm.reg.syncDoneHook != nil {
pm.reg.syncDoneHook()
if h.backend.registrar != nil && h.backend.registrar.syncDoneHook != nil {
h.backend.registrar.syncDoneHook()
}
}()
start := time.Now()
if mode == checkpointSync || mode == legacyCheckpointSync {
// Validate the advertised checkpoint
if mode == legacyCheckpointSync {
cp = params.TrustedCheckpoints[pm.blockchain.Genesis().Hash()]
cp = params.TrustedCheckpoints[h.backend.blockchain.Genesis().Hash()]
} else if mode == checkpointSync {
if err := pm.validateCheckpoint(peer); err != nil {
if err := h.validateCheckpoint(peer); err != nil {
log.Debug("Failed to validate checkpoint", "reason", err)
pm.removePeer(peer.id)
h.removePeer(peer.id)
return
}
pm.blockchain.(*light.LightChain).AddTrustedCheckpoint(cp)
h.backend.blockchain.AddTrustedCheckpoint(cp)
}
log.Debug("Checkpoint syncing start", "peer", peer.id, "checkpoint", cp.SectionIndex)
@ -182,14 +153,14 @@ func (pm *ProtocolManager) synchronise(peer *peer) {
// of the latest epoch covered by checkpoint.
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
if !cp.Empty() && !pm.blockchain.(*light.LightChain).SyncCheckpoint(ctx, cp) {
if !cp.Empty() && !h.backend.blockchain.SyncCheckpoint(ctx, cp) {
log.Debug("Sync checkpoint failed")
pm.removePeer(peer.id)
h.removePeer(peer.id)
return
}
}
// Fetch the remaining block headers based on the current chain header.
if err := pm.downloader.Synchronise(peer.id, peer.Head(), peer.Td(), downloader.LightSync); err != nil {
if err := h.downloader.Synchronise(peer.id, peer.Head(), peer.Td(), downloader.LightSync); err != nil {
log.Debug("Synchronise failed", "reason", err)
return
}

View file

@ -29,6 +29,7 @@ import (
)
func TestCheckpointSyncingLes2(t *testing.T) { testCheckpointSyncing(t, 2) }
func TestCheckpointSyncingLes3(t *testing.T) { testCheckpointSyncing(t, 3) }
func testCheckpointSyncing(t *testing.T, protocol int) {
config := light.TestServerIndexerConfig
@ -44,7 +45,7 @@ func testCheckpointSyncing(t *testing.T, protocol int) {
}
}
// Generate 512+4 blocks (totally 1 CHT sections)
server, client, tearDown := newClientServerEnv(t, int(config.ChtSize+config.ChtConfirms), protocol, waitIndexers, false)
server, client, tearDown := newClientServerEnv(t, int(config.ChtSize+config.ChtConfirms), protocol, waitIndexers, nil, false, false)
defer tearDown()
// Register checkpoint 0 into the contract at block (512+4)+1
@ -63,7 +64,7 @@ func testCheckpointSyncing(t *testing.T, protocol int) {
data := append([]byte{0x19, 0x00}, append(registrarAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...)
sig, _ := crypto.Sign(crypto.Keccak256(data), signerKey)
sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
if _, err := server.pm.reg.contract.RegisterCheckpoint(signerKey, cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil {
if _, err := server.handler.server.registrar.contract.RegisterCheckpoint(signerKey, cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil {
t.Error("register checkpoint failed", err)
}
server.backend.Commit()
@ -71,7 +72,7 @@ func testCheckpointSyncing(t *testing.T, protocol int) {
// Wait for the checkpoint registration
for {
_, hash, _, err := server.pm.reg.contract.Contract().GetLatestCheckpoint(nil)
_, hash, _, err := server.handler.server.registrar.contract.Contract().GetLatestCheckpoint(nil)
if err != nil || hash == [32]byte{} {
time.Sleep(100 * time.Millisecond)
continue
@ -80,8 +81,8 @@ func testCheckpointSyncing(t *testing.T, protocol int) {
}
done := make(chan error)
client.pm.reg.syncDoneHook = func() {
header := client.pm.blockchain.CurrentHeader()
client.handler.backend.registrar.syncDoneHook = func() {
header := client.handler.backend.blockchain.CurrentHeader()
if header.Number.Uint64() == config.ChtSize+config.ChtConfirms+2 {
done <- nil
} else {
@ -90,7 +91,7 @@ func testCheckpointSyncing(t *testing.T, protocol int) {
}
// Create connected peer pair.
peer, err1, lPeer, err2 := newTestPeerPair("peer", protocol, server.pm, client.pm)
_, err1, _, err2 := newTestPeerPair("peer", protocol, server.handler, client.handler)
select {
case <-time.After(time.Millisecond * 100):
case err := <-err1:
@ -98,7 +99,6 @@ func testCheckpointSyncing(t *testing.T, protocol int) {
case err := <-err2:
t.Fatalf("peer 2 handshake error: %v", err)
}
server.rPeer, client.rPeer = peer, lPeer
select {
case err := <-done:

View file

@ -23,7 +23,6 @@ import (
"context"
"crypto/rand"
"math/big"
"sync"
"testing"
"time"
@ -57,8 +56,8 @@ var (
userAddr1 = crypto.PubkeyToAddress(userKey1.PublicKey)
userAddr2 = crypto.PubkeyToAddress(userKey2.PublicKey)
testContractCode = common.Hex2Bytes("606060405260cc8060106000396000f360606040526000357c01000000000000000000000000000000000000000000000000000000009004806360cd2685146041578063c16431b914606b57603f565b005b6055600480803590602001909190505060a9565b6040518082815260200191505060405180910390f35b60886004808035906020019091908035906020019091905050608a565b005b80600060005083606481101560025790900160005b50819055505b5050565b6000600060005082606481101560025790900160005b5054905060c7565b91905056")
testContractAddr common.Address
testContractCode = common.Hex2Bytes("606060405260cc8060106000396000f360606040526000357c01000000000000000000000000000000000000000000000000000000009004806360cd2685146041578063c16431b914606b57603f565b005b6055600480803590602001909190505060a9565b6040518082815260200191505060405180910390f35b60886004808035906020019091908035906020019091905050608a565b005b80600060005083606481101560025790900160005b50819055505b5050565b6000600060005082606481101560025790900160005b5054905060c7565b91905056")
testContractCodeDeployed = testContractCode[16:]
testContractDeployed = uint64(2)
@ -77,8 +76,10 @@ var (
// The number of confirmations needed to generate a checkpoint(only used in test).
processConfirms = big.NewInt(4)
//
testBufLimit = uint64(1000000)
// The token bucket buffer limit for testing purpose.
testBufLimit = uint64(1000000)
// The buffer recharging speed for testing purpose.
testBufRecharge = uint64(1000)
)
@ -97,8 +98,8 @@ contract test {
}
*/
// prepareTestchain pre-commits specified number customized blocks into chain.
func prepareTestchain(n int, backend *backends.SimulatedBackend) {
// prepare pre-commits specified number customized blocks into chain.
func prepare(n int, backend *backends.SimulatedBackend) {
var (
ctx = context.Background()
signer = types.HomesteadSigner{}
@ -164,50 +165,26 @@ func testIndexers(db ethdb.Database, odr light.OdrBackend, config *light.Indexer
return indexers[:]
}
// newTestProtocolManager creates a new protocol manager for testing purposes,
// with the given number of blocks already known, potential notification
// channels for different events and relative chain indexers array.
func newTestProtocolManager(lightSync bool, blocks int, odr *LesOdr, indexers []*core.ChainIndexer, peers *peerSet, db ethdb.Database, ulcConfig *eth.ULCConfig, testCost uint64, clock mclock.Clock) (*ProtocolManager, *backends.SimulatedBackend, error) {
func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, indexers []*core.ChainIndexer, db ethdb.Database, peers *peerSet, ulcConfig *eth.ULCConfig) *clientHandler {
var (
evmux = new(event.TypeMux)
engine = ethash.NewFaker()
gspec = core.Genesis{
Config: params.AllEthashProtocolChanges,
Alloc: core.GenesisAlloc{bankAddr: {Balance: bankFunds}},
Config: params.AllEthashProtocolChanges,
Alloc: core.GenesisAlloc{bankAddr: {Balance: bankFunds}},
GasLimit: 100000000,
}
pool txPool
chain BlockChain
exitCh = make(chan struct{})
registrar *checkpointRegistrar
)
gspec.MustCommit(db)
if peers == nil {
peers = newPeerSet()
}
// create a simulation backend and pre-commit several customized block to the database.
simulation := backends.NewSimulatedBackend(db, gspec.Alloc, 100000000)
prepareTestchain(blocks, simulation)
// initialize empty chain for light client or pre-committed chain for server.
if lightSync {
chain, _ = light.NewLightChain(odr, gspec.Config, engine)
} else {
chain = simulation.Blockchain()
pool = core.NewTxPool(core.DefaultTxPoolConfig, gspec.Config, simulation.Blockchain())
}
// Create contract registrar
indexConfig := light.TestServerIndexerConfig
if lightSync {
indexConfig = light.TestClientIndexerConfig
}
config := &params.CheckpointContractConfig{
Name: "test",
ContractAddr: crypto.CreateAddress(bankAddr, 0),
Signers: []common.Address{signerAddr},
Threshold: 1,
}
var reg *checkpointRegistrar
genesis := gspec.MustCommit(db)
chain, _ := light.NewLightChain(odr, gspec.Config, engine)
if indexers != nil {
checkpointConfig := &params.CheckpointContractConfig{
Name: "test",
ContractAddr: crypto.CreateAddress(bankAddr, 0),
Signers: []common.Address{signerAddr},
Threshold: 1,
}
getLocal := func(index uint64) params.TrustedCheckpoint {
chtIndexer := indexers[0]
sectionHead := chtIndexer.SectionHead(index)
@ -218,72 +195,127 @@ func newTestProtocolManager(lightSync bool, blocks int, odr *LesOdr, indexers []
BloomRoot: light.GetBloomTrieRoot(db, index, sectionHead),
}
}
reg = newCheckpointRegistrar(config, getLocal)
registrar = newCheckpointRegistrar(checkpointConfig, getLocal)
}
pm, err := NewProtocolManager(gspec.Config, indexConfig, ulcConfig, lightSync, NetworkId, evmux, peers, chain, pool, db, odr, nil, reg, exitCh, new(sync.WaitGroup), func() bool { return true })
if err != nil {
return nil, nil, err
client := &LightEthereum{
lesCommons: lesCommons{
genesis: genesis.Hash(),
config: &eth.Config{LightPeers: 100, NetworkId: NetworkId},
chainConfig: params.AllEthashProtocolChanges,
iConfig: light.TestClientIndexerConfig,
chainDb: db,
registrar: registrar,
chainReader: chain,
peers: peers,
closeCh: make(chan struct{}),
},
reqDist: odr.retriever.dist,
retriever: odr.retriever,
odr: odr,
engine: engine,
blockchain: chain,
eventMux: evmux,
}
// Registrar initialization could failed if checkpoint contract is not specified.
if pm.reg != nil {
pm.reg.start(simulation)
}
// Set up les server stuff.
if !lightSync {
srv := &LesServer{lesCommons: lesCommons{protocolManager: pm, chainDb: db}}
pm.server = srv
pm.servingQueue = newServingQueue(int64(time.Millisecond*10), 1, nil)
pm.servingQueue.setThreads(4)
client.handler = newClientHandler(ulcConfig, client)
srv.defParams = flowcontrol.ServerParams{
BufLimit: testBufLimit,
MinRecharge: testBufRecharge,
}
srv.testCost = testCost
srv.fcManager = flowcontrol.NewClientManager(nil, clock)
if client.registrar != nil {
client.registrar.start(backend)
}
pm.Start(1000)
return pm, simulation, nil
return client.handler
}
// newTestProtocolManagerMust creates a new protocol manager for testing purposes,
// with the given number of blocks already known, potential notification channels
// for different events and relative chain indexers array. In case of an error, the
// constructor force-fails the test.
func newTestProtocolManagerMust(t *testing.T, lightSync bool, blocks int, odr *LesOdr, indexers []*core.ChainIndexer, peers *peerSet, db ethdb.Database, ulcConfig *eth.ULCConfig) (*ProtocolManager, *backends.SimulatedBackend) {
pm, backend, err := newTestProtocolManager(lightSync, blocks, odr, indexers, peers, db, ulcConfig, 0, &mclock.System{})
if err != nil {
t.Fatalf("Failed to create protocol manager: %v", err)
func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Database, peers *peerSet, clock mclock.Clock) (*serverHandler, *backends.SimulatedBackend) {
var (
gspec = core.Genesis{
Config: params.AllEthashProtocolChanges,
Alloc: core.GenesisAlloc{bankAddr: {Balance: bankFunds}},
GasLimit: 100000000,
}
registrar *checkpointRegistrar
)
genesis := gspec.MustCommit(db)
// create a simulation backend and pre-commit several customized block to the database.
simulation := backends.NewSimulatedBackend(db, gspec.Alloc, 100000000)
prepare(blocks, simulation)
txpoolConfig := core.DefaultTxPoolConfig
txpoolConfig.Journal = ""
txpool := core.NewTxPool(txpoolConfig, gspec.Config, simulation.Blockchain())
if indexers != nil {
checkpointConfig := &params.CheckpointContractConfig{
Name: "test",
ContractAddr: crypto.CreateAddress(bankAddr, 0),
Signers: []common.Address{signerAddr},
Threshold: 1,
}
getLocal := func(index uint64) params.TrustedCheckpoint {
chtIndexer := indexers[0]
sectionHead := chtIndexer.SectionHead(index)
return params.TrustedCheckpoint{
SectionIndex: index,
SectionHead: sectionHead,
CHTRoot: light.GetChtRoot(db, index, sectionHead),
BloomRoot: light.GetBloomTrieRoot(db, index, sectionHead),
}
}
registrar = newCheckpointRegistrar(checkpointConfig, getLocal)
}
return pm, backend
server := &LesServer{
lesCommons: lesCommons{
genesis: genesis.Hash(),
config: &eth.Config{LightPeers: 100, NetworkId: NetworkId},
chainConfig: params.AllEthashProtocolChanges,
iConfig: light.TestServerIndexerConfig,
chainDb: db,
chainReader: simulation.Blockchain(),
registrar: registrar,
peers: peers,
closeCh: make(chan struct{}),
},
servingQueue: newServingQueue(int64(time.Millisecond*10), 1, nil),
defParams: flowcontrol.ServerParams{
BufLimit: testBufLimit,
MinRecharge: testBufRecharge,
},
fcManager: flowcontrol.NewClientManager(nil, clock),
}
server.costTracker, server.minCapacity = newCostTracker(db, server.config, nil)
server.costTracker.costListHook = func() RequestCostList { return testCostList(0) } // Disable flow control mechanism.
server.handler = newServerHandler(server, simulation.Blockchain(), db, txpool, nil, func() bool { return true })
if server.registrar != nil {
server.registrar.start(simulation)
}
server.servingQueue.setThreads(4)
server.handler.start()
return server.handler, simulation
}
// testPeer is a simulated peer to allow testing direct network calls.
type testPeer struct {
peer *peer
net p2p.MsgReadWriter // Network layer reader/writer to simulate remote messaging
app *p2p.MsgPipeRW // Application layer reader/writer to simulate the local side
*peer
}
// newTestPeer creates a new peer registered at the given protocol manager.
func newTestPeer(t *testing.T, name string, version int, pm *ProtocolManager, shake bool, testCost uint64) (*testPeer, <-chan error) {
func newTestPeer(t *testing.T, name string, version int, handler *serverHandler, shake bool, testCost uint64) (*testPeer, <-chan error) {
// 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[:])
peer := pm.newPeer(version, NetworkId, p2p.NewPeer(id, name, nil), net)
peer := newPeer(version, NetworkId, false, p2p.NewPeer(id, name, nil), net)
// Start the peer on a new thread
errc := make(chan error, 1)
errCh := make(chan error, 1)
go func() {
select {
case pm.newPeerCh <- peer:
errc <- pm.handle(peer)
case <-pm.quitSync:
errc <- p2p.DiscQuitting
case <-handler.closeCh:
errCh <- p2p.DiscQuitting
case errCh <- handler.handle(peer):
}
}()
tp := &testPeer{
@ -293,17 +325,28 @@ func newTestPeer(t *testing.T, name string, version int, pm *ProtocolManager, sh
}
// Execute any implicitly requested handshakes and return
if shake {
// Customize the cost table generation function.
if testCost != 0 {
handler.server.costTracker.costListHook = func() RequestCostList { return testCostList(testCost) }
}
var (
genesis = pm.blockchain.Genesis()
head = pm.blockchain.CurrentHeader()
td = pm.blockchain.GetTd(head.Hash(), head.Number.Uint64())
genesis = handler.blockchain.Genesis()
head = handler.blockchain.CurrentHeader()
td = handler.blockchain.GetTd(head.Hash(), head.Number.Uint64())
costList = handler.server.costTracker.costListHook()
)
tp.handshake(t, td, head.Hash(), head.Number.Uint64(), genesis.Hash(), testCost)
tp.handshake(t, td, head.Hash(), head.Number.Uint64(), genesis.Hash(), costList)
}
return tp, errc
return tp, errCh
}
func newTestPeerPair(name string, version int, pm, pm2 *ProtocolManager) (*peer, <-chan error, *peer, <-chan error) {
// close terminates the local side of the peer, notifying the remote protocol
// manager of termination.
func (p *testPeer) close() {
p.app.Close()
}
func newTestPeerPair(name string, version int, server *serverHandler, client *clientHandler) (*testPeer, <-chan error, *testPeer, <-chan error) {
// Create a message pipe to communicate through
app, net := p2p.MsgPipe()
@ -311,36 +354,34 @@ func newTestPeerPair(name string, version int, pm, pm2 *ProtocolManager) (*peer,
var id enode.ID
rand.Read(id[:])
peer := pm.newPeer(version, NetworkId, p2p.NewPeer(id, name, nil), net)
peer2 := pm2.newPeer(version, NetworkId, p2p.NewPeer(id, name, nil), app)
peer1 := newPeer(version, NetworkId, false, p2p.NewPeer(id, name, nil), net)
peer2 := newPeer(version, NetworkId, false, p2p.NewPeer(id, name, nil), app)
// Start the peer on a new thread
errc := make(chan error, 1)
errc1 := make(chan error, 1)
errc2 := make(chan error, 1)
go func() {
select {
case pm.newPeerCh <- peer:
errc <- pm.handle(peer)
case <-pm.quitSync:
errc <- p2p.DiscQuitting
case <-server.closeCh:
errc1 <- p2p.DiscQuitting
case errc1 <- server.handle(peer1):
}
}()
go func() {
select {
case pm2.newPeerCh <- peer2:
errc2 <- pm2.handle(peer2)
case <-pm2.quitSync:
errc2 <- p2p.DiscQuitting
case <-client.closeCh:
errc1 <- p2p.DiscQuitting
case errc1 <- client.handle(peer2):
}
}()
return peer, errc, peer2, errc2
return &testPeer{peer: peer1, net: net, app: app}, errc1, &testPeer{peer: peer2, net: app, app: net}, errc2
}
// handshake simulates a trivial handshake that expects the same state from the
// remote side as we are simulating locally.
func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, testCost uint64) {
func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, costList RequestCostList) {
var expList keyValueList
expList = expList.add("protocolVersion", uint64(p.version))
expList = expList.add("protocolVersion", uint64(p.peer.version))
expList = expList.add("networkId", uint64(NetworkId))
expList = expList.add("headTd", td)
expList = expList.add("headHash", head)
@ -355,7 +396,7 @@ func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, headNu
expList = expList.add("txRelay", nil)
expList = expList.add("flowControl/BL", testBufLimit)
expList = expList.add("flowControl/MRR", testBufRecharge)
expList = expList.add("flowControl/MRC", testCostList(testCost))
expList = expList.add("flowControl/MRC", costList)
if err := p2p.ExpectMsg(p.app, StatusMsg, expList); err != nil {
t.Fatalf("status recv: %v", err)
@ -363,112 +404,120 @@ func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, headNu
if err := p2p.Send(p.app, StatusMsg, sendList); err != nil {
t.Fatalf("status send: %v", err)
}
p.fcParams = flowcontrol.ServerParams{
p.peer.fcParams = flowcontrol.ServerParams{
BufLimit: testBufLimit,
MinRecharge: testBufRecharge,
}
}
// close terminates the local side of the peer, notifying the remote protocol
// manager of termination.
func (p *testPeer) close() {
p.app.Close()
}
type indexerCallback func(*core.ChainIndexer, *core.ChainIndexer, *core.ChainIndexer)
// TestEntity represents a network entity for testing with necessary auxiliary fields.
type TestEntity struct {
// testClient represents a client for testing with necessary auxiliary fields.
type testClient struct {
clock mclock.Clock
db ethdb.Database
rPeer *peer
tPeer *testPeer
peers *peerSet
pm *ProtocolManager
backend *backends.SimulatedBackend
peer *testPeer
handler *clientHandler
// Indexers
chtIndexer *core.ChainIndexer
bloomIndexer *core.ChainIndexer
bloomTrieIndexer *core.ChainIndexer
}
// newServerEnv creates a server testing environment with a connected test peer for testing purpose.
func newServerEnv(t *testing.T, blocks int, protocol int, waitIndexers func(*core.ChainIndexer, *core.ChainIndexer, *core.ChainIndexer)) (*TestEntity, func()) {
// testServer represents a server for testing with necessary auxiliary fields.
type testServer struct {
clock mclock.Clock
backend *backends.SimulatedBackend
db ethdb.Database
peer *testPeer
handler *serverHandler
chtIndexer *core.ChainIndexer
bloomIndexer *core.ChainIndexer
bloomTrieIndexer *core.ChainIndexer
}
func newServerEnv(t *testing.T, blocks int, protocol int, callback indexerCallback, simClock bool, newPeer bool, testCost uint64) (*testServer, func()) {
db := rawdb.NewMemoryDatabase()
indexers := testIndexers(db, nil, light.TestServerIndexerConfig)
pm, b := newTestProtocolManagerMust(t, false, blocks, nil, indexers, nil, db, nil)
peer, _ := newTestPeer(t, "peer", protocol, pm, true, 0)
var clock mclock.Clock
clock = &mclock.System{}
if simClock {
clock = &mclock.Simulated{}
}
handler, backend := newTestServerHandler(blocks, indexers, db, newPeerSet(), clock)
var peer *testPeer
if newPeer {
peer, _ = newTestPeer(t, "peer", protocol, handler, true, testCost)
}
cIndexer, bIndexer, btIndexer := indexers[0], indexers[1], indexers[2]
cIndexer.Start(pm.blockchain.(*core.BlockChain))
bIndexer.Start(pm.blockchain.(*core.BlockChain))
cIndexer.Start(handler.blockchain)
bIndexer.Start(handler.blockchain)
// Wait until indexers generate enough index data.
if waitIndexers != nil {
waitIndexers(cIndexer, bIndexer, btIndexer)
if callback != nil {
callback(cIndexer, bIndexer, btIndexer)
}
return &TestEntity{
db: db,
tPeer: peer,
pm: pm,
backend: b,
chtIndexer: cIndexer,
bloomIndexer: bIndexer,
bloomTrieIndexer: btIndexer,
}, func() {
server := &testServer{
clock: clock,
backend: backend,
db: db,
peer: peer,
handler: handler,
chtIndexer: cIndexer,
bloomIndexer: bIndexer,
bloomTrieIndexer: btIndexer,
}
teardown := func() {
if newPeer {
peer.close()
// Note bloom trie indexer will be closed by it parent recursively.
cIndexer.Close()
bIndexer.Close()
}
cIndexer.Close()
bIndexer.Close()
}
return server, teardown
}
// newClientServerEnv creates a client/server arch environment with a connected les server and light client pair
// for testing purpose.
func newClientServerEnv(t *testing.T, blocks int, protocol int, waitIndexers func(*core.ChainIndexer, *core.ChainIndexer, *core.ChainIndexer), newPeer bool) (*TestEntity, *TestEntity, func()) {
db, ldb := rawdb.NewMemoryDatabase(), rawdb.NewMemoryDatabase()
peers, lPeers := newPeerSet(), newPeerSet()
func newClientServerEnv(t *testing.T, blocks int, protocol int, callback indexerCallback, ulcConfig *eth.ULCConfig, simClock bool, connect bool) (*testServer, *testClient, func()) {
sdb, cdb := rawdb.NewMemoryDatabase(), rawdb.NewMemoryDatabase()
speers, cPeers := newPeerSet(), newPeerSet()
dist := newRequestDistributor(lPeers, make(chan struct{}), &mclock.System{})
rm := newRetrieveManager(lPeers, dist, nil)
odr := NewLesOdr(ldb, light.TestClientIndexerConfig, rm)
indexers := testIndexers(db, nil, light.TestServerIndexerConfig)
lIndexers := testIndexers(ldb, odr, light.TestClientIndexerConfig)
cIndexer, bIndexer, btIndexer := indexers[0], indexers[1], indexers[2]
lcIndexer, lbIndexer, lbtIndexer := lIndexers[0], lIndexers[1], lIndexers[2]
odr.SetIndexers(lcIndexer, lbtIndexer, lbIndexer)
pm, b := newTestProtocolManagerMust(t, false, blocks, nil, indexers, peers, db, nil)
lpm, lb := newTestProtocolManagerMust(t, true, 0, odr, lIndexers, lPeers, ldb, nil)
startIndexers := func(clientMode bool, pm *ProtocolManager) {
if clientMode {
lcIndexer.Start(pm.blockchain.(*light.LightChain))
lbIndexer.Start(pm.blockchain.(*light.LightChain))
} else {
cIndexer.Start(pm.blockchain.(*core.BlockChain))
bIndexer.Start(pm.blockchain.(*core.BlockChain))
}
var clock mclock.Clock
clock = &mclock.System{}
if simClock {
clock = &mclock.Simulated{}
}
dist := newRequestDistributor(cPeers, clock)
rm := newRetrieveManager(cPeers, dist, nil)
odr := NewLesOdr(cdb, light.TestClientIndexerConfig, rm)
startIndexers(false, pm)
startIndexers(true, lpm)
sindexers := testIndexers(sdb, nil, light.TestServerIndexerConfig)
cIndexers := testIndexers(cdb, odr, light.TestClientIndexerConfig)
// Execute wait until function if it is specified.
if waitIndexers != nil {
waitIndexers(cIndexer, bIndexer, btIndexer)
scIndexer, sbIndexer, sbtIndexer := sindexers[0], sindexers[1], sindexers[2]
ccIndexer, cbIndexer, cbtIndexer := cIndexers[0], cIndexers[1], cIndexers[2]
odr.SetIndexers(ccIndexer, cbIndexer, cbtIndexer)
server, backend := newTestServerHandler(blocks, sindexers, sdb, speers, clock)
client := newTestClientHandler(backend, odr, cIndexers, cdb, cPeers, ulcConfig)
scIndexer.Start(server.blockchain)
sbIndexer.Start(server.blockchain)
ccIndexer.Start(client.backend.blockchain)
cbIndexer.Start(client.backend.blockchain)
if callback != nil {
callback(scIndexer, sbIndexer, sbtIndexer)
}
var (
peer, lPeer *peer
err1, err2 <-chan error
speer, cpeer *testPeer
err1, err2 <-chan error
)
if newPeer {
peer, err1, lPeer, err2 = newTestPeerPair("peer", protocol, pm, lpm)
if connect {
cpeer, err1, speer, err2 = newTestPeerPair("peer", protocol, server, client)
select {
case <-time.After(time.Millisecond * 100):
case err := <-err1:
@ -477,30 +526,34 @@ func newClientServerEnv(t *testing.T, blocks int, protocol int, waitIndexers fun
t.Fatalf("peer 2 handshake error: %v", err)
}
}
return &TestEntity{
db: db,
pm: pm,
rPeer: peer,
peers: peers,
backend: b,
chtIndexer: cIndexer,
bloomIndexer: bIndexer,
bloomTrieIndexer: btIndexer,
}, &TestEntity{
db: ldb,
pm: lpm,
rPeer: lPeer,
peers: lPeers,
backend: lb,
chtIndexer: lcIndexer,
bloomIndexer: lbIndexer,
bloomTrieIndexer: lbtIndexer,
}, func() {
// Note bloom trie indexers will be closed by their parents recursively.
cIndexer.Close()
bIndexer.Close()
lcIndexer.Close()
lbIndexer.Close()
s := &testServer{
clock: clock,
backend: backend,
db: sdb,
peer: cpeer,
handler: server,
chtIndexer: scIndexer,
bloomIndexer: sbIndexer,
bloomTrieIndexer: sbtIndexer,
}
c := &testClient{
clock: clock,
db: cdb,
peer: speer,
handler: client,
chtIndexer: ccIndexer,
bloomIndexer: cbIndexer,
bloomTrieIndexer: cbtIndexer,
}
teardown := func() {
if connect {
speer.close()
cpeer.close()
}
ccIndexer.Close()
cbIndexer.Close()
scIndexer.Close()
sbIndexer.Close()
}
return s, c, teardown
}

View file

View file

@ -130,7 +130,7 @@ func (self *lesTxRelay) send(txs types.Transactions, count int) {
return peer.GetTxRelayCost(len(ll), len(enc))
},
canSend: func(dp distPeer) bool {
return !dp.(*peer).isOnlyAnnounce && dp.(*peer) == pp
return !dp.(*peer).announceOnly && dp.(*peer) == pp
},
request: func(dp distPeer) func() {
peer := dp.(*peer)

View file

@ -44,7 +44,7 @@ func newULC(ulcConfig *eth.ULCConfig) *ulc {
return &ulc{m, ulcConfig.MinTrustedFraction}
}
// isTrusted return an indicator that whether the specified peer is trusted.
// trusted return an indicator that whether the specified peer is trusted.
func (u *ulc) isTrusted(p enode.ID) bool {
if u.trustedKeys == nil {
return false

View file

@ -17,184 +17,105 @@
package les
import (
"crypto/ecdsa"
"crypto/rand"
"fmt"
"math/big"
"net"
"reflect"
"testing"
"time"
"github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode"
)
func TestULCSyncWithOnePeer(t *testing.T) {
f := newFullPeerPair(t, 1, 4)
ulcConfig := &eth.ULCConfig{
MinTrustedFraction: 100,
TrustedServers: []string{f.Node.String()},
func TestULCAnnounceThresholdLes2(t *testing.T) { testULCAnnounceThreshold(t, 2) }
func TestULCAnnounceThresholdLes3(t *testing.T) { testULCAnnounceThreshold(t, 3) }
func testULCAnnounceThreshold(t *testing.T, protocol int) {
// todo figure out why it takes fetcher so longer to fetcher the announced header.
t.Skip("Sometimes it can failed")
var cases = []struct {
height []int
threshold int
expect uint64
}{
{[]int{1}, 100, 1},
{[]int{0, 0, 0}, 100, 0},
{[]int{1, 2, 3}, 30, 3},
{[]int{1, 2, 3}, 60, 2},
{[]int{3, 2, 1}, 67, 1},
{[]int{3, 2, 1}, 100, 1},
}
for _, testcase := range cases {
var (
servers []*testServer
teardowns []func()
nodes []*enode.Node
ids []string
)
for i := 0; i < len(testcase.height); i++ {
s, n, teardown := newServerPeer(t, 0, protocol)
l := newLightPeer(t, ulcConfig)
servers = append(servers, s)
nodes = append(nodes, n)
teardowns = append(teardowns, teardown)
ids = append(ids, n.String())
}
config := &eth.ULCConfig{
MinTrustedFraction: testcase.threshold,
TrustedServers: ids,
}
c, teardown := newLightPeer(t, protocol, config)
if reflect.DeepEqual(f.PM.blockchain.CurrentHeader().Hash(), l.PM.blockchain.CurrentHeader().Hash()) {
t.Fatal("blocks are equal")
}
// Connect all servers.
for i := 0; i < len(servers); i++ {
connect(servers[i].handler, nodes[i].ID(), c.handler, protocol)
}
for i := 0; i < len(servers); i++ {
for j := 0; j < testcase.height[i]; j++ {
servers[i].backend.InsertEmptyBlocks(1)
}
}
time.Sleep(1500 * time.Millisecond) // Ensure the fetcher has done its work.
head := c.handler.backend.blockchain.CurrentHeader().Number.Uint64()
if head != testcase.expect {
t.Fatalf("chain height mismatch, want %d, got %d", testcase.expect, head)
}
_, _, err := connectPeers(f, l, 2)
if err != nil {
t.Fatal(err)
}
l.PM.fetcher.lock.Lock()
l.PM.fetcher.nextRequest()
l.PM.fetcher.lock.Unlock()
if !reflect.DeepEqual(f.PM.blockchain.CurrentHeader().Hash(), l.PM.blockchain.CurrentHeader().Hash()) {
t.Fatal("sync doesn't work")
// Release all servers and client resources.
teardown()
for i := 0; i < len(teardowns); i++ {
teardowns[i]()
}
}
}
func TestULCReceiveAnnounce(t *testing.T) {
f := newFullPeerPair(t, 1, 4)
ulcConfig := &eth.ULCConfig{
MinTrustedFraction: 100,
TrustedServers: []string{f.Node.String()},
}
l := newLightPeer(t, ulcConfig)
fPeer, lPeer, err := connectPeers(f, l, 2)
if err != nil {
t.Fatal(err)
}
l.PM.synchronise(fPeer)
//check that the sync is finished correctly
if !reflect.DeepEqual(f.PM.blockchain.CurrentHeader().Hash(), l.PM.blockchain.CurrentHeader().Hash()) {
t.Fatal("sync doesn't work")
}
l.PM.peers.lock.Lock()
if len(l.PM.peers.peers) == 0 {
t.Fatal("peer list should not be empty")
}
l.PM.peers.lock.Unlock()
time.Sleep(time.Second)
//send a signed announce message(payload doesn't matter)
td := f.PM.blockchain.GetTd(l.PM.blockchain.CurrentHeader().Hash(), l.PM.blockchain.CurrentHeader().Number.Uint64())
announce := announceData{
Number: l.PM.blockchain.CurrentHeader().Number.Uint64() + 1,
Td: td.Add(td, big.NewInt(1)),
}
announce.sign(f.Key)
lPeer.SendAnnounce(announce)
}
func TestULCShouldNotSyncWithTwoPeersOneHaveEmptyChain(t *testing.T) {
f1 := newFullPeerPair(t, 1, 4)
f2 := newFullPeerPair(t, 2, 0)
ulcConf := &ulc{minTrustedFraction: 100, trustedKeys: make(map[string]struct{})}
ulcConf.trustedKeys[f1.Node.ID().String()] = struct{}{}
ulcConf.trustedKeys[f2.Node.ID().String()] = struct{}{}
ulcConfig := &eth.ULCConfig{
MinTrustedFraction: 100,
TrustedServers: []string{f1.Node.String(), f2.Node.String()},
}
l := newLightPeer(t, ulcConfig)
l.PM.ulc.minTrustedFraction = 100
_, _, err := connectPeers(f1, l, 2)
if err != nil {
t.Fatal(err)
}
_, _, err = connectPeers(f2, l, 2)
if err != nil {
t.Fatal(err)
}
l.PM.fetcher.lock.Lock()
l.PM.fetcher.nextRequest()
l.PM.fetcher.lock.Unlock()
if reflect.DeepEqual(f2.PM.blockchain.CurrentHeader().Hash(), l.PM.blockchain.CurrentHeader().Hash()) {
t.Fatal("Incorrect hash: second peer has empty chain")
}
}
func TestULCShouldNotSyncWithThreePeersOneHaveEmptyChain(t *testing.T) {
f1 := newFullPeerPair(t, 1, 3)
f2 := newFullPeerPair(t, 2, 4)
f3 := newFullPeerPair(t, 3, 0)
ulcConfig := &eth.ULCConfig{
MinTrustedFraction: 60,
TrustedServers: []string{f1.Node.String(), f2.Node.String(), f3.Node.String()},
}
l := newLightPeer(t, ulcConfig)
_, _, err := connectPeers(f1, l, 2)
if err != nil {
t.Fatal(err)
}
_, _, err = connectPeers(f2, l, 2)
if err != nil {
t.Fatal(err)
}
_, _, err = connectPeers(f3, l, 2)
if err != nil {
t.Fatal(err)
}
l.PM.fetcher.lock.Lock()
l.PM.fetcher.nextRequest()
l.PM.fetcher.lock.Unlock()
if !reflect.DeepEqual(f1.PM.blockchain.CurrentHeader().Hash(), l.PM.blockchain.CurrentHeader().Hash()) {
t.Fatal("Incorrect hash")
}
}
type pairPeer struct {
Name string
Node *enode.Node
PM *ProtocolManager
Key *ecdsa.PrivateKey
}
func connectPeers(full, light pairPeer, version int) (*peer, *peer, error) {
func connect(server *serverHandler, serverId enode.ID, client *clientHandler, protocol int) (*peer, *peer, error) {
// Create a message pipe to communicate through
app, net := p2p.MsgPipe()
peerLight := full.PM.newPeer(version, NetworkId, p2p.NewPeer(light.Node.ID(), light.Name, nil), net)
peerFull := light.PM.newPeer(version, NetworkId, p2p.NewPeer(full.Node.ID(), full.Name, nil), app)
var id enode.ID
rand.Read(id[:])
peer1 := newPeer(protocol, NetworkId, true, p2p.NewPeer(serverId, "", nil), net) // Mark server as trusted
peer2 := newPeer(protocol, NetworkId, false, p2p.NewPeer(id, "", nil), app)
// Start the peerLight on a new thread
errc1 := make(chan error, 1)
errc2 := make(chan error, 1)
go func() {
select {
case light.PM.newPeerCh <- peerFull:
errc1 <- light.PM.handle(peerFull)
case <-light.PM.quitSync:
case <-server.closeCh:
errc1 <- p2p.DiscQuitting
case errc1 <- server.handle(peer2):
}
}()
go func() {
select {
case full.PM.newPeerCh <- peerLight:
errc2 <- full.PM.handle(peerLight)
case <-full.PM.quitSync:
errc2 <- p2p.DiscQuitting
case <-client.closeCh:
errc1 <- p2p.DiscQuitting
case errc1 <- client.handle(peer1):
}
}()
@ -205,49 +126,23 @@ func connectPeers(full, light pairPeer, version int) (*peer, *peer, error) {
case err := <-errc2:
return nil, nil, fmt.Errorf("peerFull handshake error: %v", err)
}
return peerFull, peerLight, nil
return peer1, peer2, nil
}
// newFullPeerPair creates node with full sync mode
func newFullPeerPair(t *testing.T, index int, numberOfblocks int) pairPeer {
db := rawdb.NewMemoryDatabase()
pmFull, _ := newTestProtocolManagerMust(t, false, numberOfblocks, nil, nil, nil, db, nil)
peerPairFull := pairPeer{
Name: "full node",
PM: pmFull,
}
// newServerPeer creates server peer.
func newServerPeer(t *testing.T, blocks int, protocol int) (*testServer, *enode.Node, func()) {
s, teardown := newServerEnv(t, blocks, protocol, nil, false, false, 0)
key, err := crypto.GenerateKey()
if err != nil {
t.Fatal("generate key err:", err)
}
peerPairFull.Key = key
peerPairFull.Node = enode.NewV4(&key.PublicKey, net.ParseIP("127.0.0.1"), 35000, 35000)
return peerPairFull
s.handler.server.privateKey = key
n := enode.NewV4(&key.PublicKey, net.ParseIP("127.0.0.1"), 35000, 35000)
return s, n, teardown
}
// newLightPeer creates node with light sync mode
func newLightPeer(t *testing.T, ulcConfig *eth.ULCConfig) pairPeer {
peers := newPeerSet()
dist := newRequestDistributor(peers, make(chan struct{}), &mclock.System{})
rm := newRetrieveManager(peers, dist, nil)
ldb := rawdb.NewMemoryDatabase()
odr := NewLesOdr(ldb, light.DefaultClientIndexerConfig, rm)
pmLight, _ := newTestProtocolManagerMust(t, true, 0, odr, nil, peers, ldb, ulcConfig)
peerPairLight := pairPeer{
Name: "ulc node",
PM: pmLight,
}
key, err := crypto.GenerateKey()
if err != nil {
t.Fatal("generate key err:", err)
}
peerPairLight.Key = key
peerPairLight.Node = enode.NewV4(&key.PublicKey, net.IP{}, 35000, 35000)
return peerPairLight
func newLightPeer(t *testing.T, protocol int, ulcConfig *eth.ULCConfig) (*testClient, func()) {
_, c, teardown := newClientServerEnv(t, 0, protocol, nil, ulcConfig, false, false)
return c, teardown
}