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 // newPriorityClientPool creates a new priority client pool
func newPriorityClientPool(freeClientCap uint64, ps *peerSet, child clientPool, metricsLogger, eventLogger *csvlogger.Logger) *priorityClientPool { func newPriorityClientPool(freeClientCap uint64, ps *peerSet, child clientPool, eventLogger *csvlogger.Logger, metricsLogger *csvlogger.Logger) *priorityClientPool {
return &priorityClientPool{ pool := &priorityClientPool{
clients: make(map[enode.ID]priorityClientInfo), clients: make(map[enode.ID]priorityClientInfo),
freeClientCap: freeClientCap, freeClientCap: freeClientCap,
ps: ps, ps: ps,
@ -179,6 +179,8 @@ func newPriorityClientPool(freeClientCap uint64, ps *peerSet, child clientPool,
logger: eventLogger, logger: eventLogger,
logTotalPriConn: metricsLogger.NewChannel("totalPriConn", 0), logTotalPriConn: metricsLogger.NewChannel("totalPriConn", 0),
} }
ps.notify(pool)
return pool
} }
// registerPeer is called when a new client is connected. If the client has no // 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 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)) result := make([]map[string]interface{}, len(setups))
for i, r := range rs { for i, r := range rs {
res := make(map[string]interface{}) 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. // PrivateLightAPI provides an API to access the LES light server or light client.
type PrivateLightAPI struct { type PrivateLightAPI struct {
backend *lesCommons backend *lesCommons
reg *checkpointRegistrar
} }
// NewPrivateLightAPI creates a new LES service API. // NewPrivateLightAPI creates a new LES service API.
func NewPrivateLightAPI(backend *lesCommons, reg *checkpointRegistrar) *PrivateLightAPI { func NewPrivateLightAPI(backend *lesCommons) *PrivateLightAPI {
return &PrivateLightAPI{ return &PrivateLightAPI{
backend: backend, 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 // result[2], 32 bytes hex encoded latest section bloom trie root hash
func (api *PrivateLightAPI) GetCheckpoint(index uint64) ([3]string, error) { func (api *PrivateLightAPI) GetCheckpoint(index uint64) ([3]string, error) {
var res [3]string var res [3]string
cp := api.backend.getLocalCheckpoint(index) cp := api.backend.localCheckpoint(index)
if cp.Empty() { if cp.Empty() {
return res, ErrNoCheckpoint 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. // GetCheckpointContractAddress returns the contract contract address in hex format.
func (api *PrivateLightAPI) GetCheckpointContractAddress() (string, error) { func (api *PrivateLightAPI) GetCheckpointContractAddress() (string, error) {
if api.reg == nil { if api.backend.registrar == nil {
return "", ErrNotActivated 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) { func (b *LesApiBackend) SetHead(number uint64) {
b.eth.protocolManager.downloader.Cancel() b.eth.handler.downloader.Cancel()
b.eth.blockchain.SetHead(number) 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 // while connected and going back and forth between free and priority mode with
// the supplied API calls is also thoroughly tested. // the supplied API calls is also thoroughly tested.
func testCapacityAPI(t *testing.T, clientCount int) { func testCapacityAPI(t *testing.T, clientCount int) {
if testServerDataDir == "" {
// Skip test if no data dir specified // Skip test if no data dir specified
if testServerDataDir == "" {
return return
} }
for !testSim(t, 1, clientCount, []string{testServerDataDir}, nil, func(ctx context.Context, net *simulations.Network, servers []*simulations.Node, clients []*simulations.Node) bool { 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 { if len(servers) != 1 {
t.Fatalf("Invalid number of servers: %d", len(servers)) t.Fatalf("Invalid number of servers: %d", len(servers))
} }
server := servers[0] server := servers[0]
clientRpcClients := make([]*rpc.Client, len(clients))
serverRpcClient, err := server.Client() serverRpcClient, err := server.Client()
if err != nil { if err != nil {
t.Fatalf("Failed to obtain rpc client: %v", err) 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) minCap := getMinCap(ctx, t, serverRpcClient)
testCap := totalCap * 3 / 4 testCap := totalCap * 3 / 4
fmt.Printf("Server testCap: %d minCap: %d head number: %d head hash: %064x\n", testCap, minCap, headNum, headHash) 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))) reqMinCap := uint64(float64(testCap) * minRelCap / (minRelCap + float64(len(clients)-1)))
if minCap > reqMinCap { if minCap > reqMinCap {
t.Fatalf("Minimum client capacity (%d) bigger than required minimum for this test (%d)", 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)) freeIdx := rand.Intn(len(clients))
freeCap := getFreeCap(ctx, t, serverRpcClient) freeCap := getFreeCap(ctx, t, serverRpcClient)
clientRpcClients := make([]*rpc.Client, len(clients))
for i, client := range clients { for i, client := range clients {
var err error var err error
clientRpcClients[i], err = client.Client() clientRpcClients[i], err = client.Client()
if err != nil { if err != nil {
t.Fatalf("Failed to obtain rpc client: %v", err) t.Fatalf("Failed to obtain rpc client: %v", err)
} }
fmt.Println("connecting client", i) fmt.Println("connecting client", i)
if i != freeIdx { if i != freeIdx {
setCapacity(ctx, t, serverRpcClient, client.ID(), testCap/uint64(len(clients))) 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)) reqCount := make([]uint64, len(clientRpcClients))
// Send light request like crazy.
for i, c := range clientRpcClients { for i, c := range clientRpcClients {
wg.Add(1) wg.Add(1)
i, c := i, c i, c := i, c
go func() { go func() {
defer wg.Done()
queue := make(chan struct{}, 100) queue := make(chan struct{}, 100)
var count uint64 var count uint64
for { for {
@ -153,10 +154,8 @@ func testCapacityAPI(t *testing.T, clientCount int) {
case queue <- struct{}{}: case queue <- struct{}{}:
select { select {
case <-stop: case <-stop:
wg.Done()
return return
case <-ctx.Done(): case <-ctx.Done():
wg.Done()
return return
default: default:
wg.Add(1) wg.Add(1)
@ -171,10 +170,8 @@ func testCapacityAPI(t *testing.T, clientCount int) {
}() }()
} }
case <-stop: case <-stop:
wg.Done()
return return
case <-ctx.Done(): case <-ctx.Done():
wg.Done()
return 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 { func testRequest(ctx context.Context, t *testing.T, client *rpc.Client) bool {
//res := make(map[string]interface{})
var res string var res string
var addr common.Address var addr common.Address
rand.Read(addr[:]) rand.Read(addr[:])
c, _ := context.WithTimeout(ctx, time.Second*12) 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") err := client.CallContext(c, &res, "eth_getBalance", addr, "latest")
if err != nil { if err != nil {
fmt.Println("request error:", err) fmt.Println("request error:", err)
@ -417,7 +412,6 @@ func NewNetwork() (*simulations.Network, func(), error) {
adapterTeardown() adapterTeardown()
net.Shutdown() net.Shutdown()
} }
return net, teardown, nil return net, teardown, nil
} }
@ -515,7 +509,6 @@ func newLesServerService(ctx *adapters.ServiceContext) (node.Service, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
server, err := NewLesServer(ethereum, &config) server, err := NewLesServer(ethereum, &config)
if err != nil { if err != nil {
return nil, err return nil, err

View file

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

View file

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

View file

@ -19,8 +19,6 @@ package les
import ( import (
"fmt" "fmt"
"sync"
"time"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
@ -42,7 +40,7 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p" "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/params"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
@ -50,33 +48,23 @@ import (
type LightEthereum struct { type LightEthereum struct {
lesCommons lesCommons
reqDist *requestDistributor
retriever *retrieveManager
odr *LesOdr odr *LesOdr
chainConfig *params.ChainConfig relay *lesTxRelay
// Channel for shutting down the service handler *clientHandler
shutdownChan chan bool
// Handlers
peers *peerSet
txPool *light.TxPool txPool *light.TxPool
blockchain *light.LightChain blockchain *light.LightChain
serverPool *serverPool serverPool *serverPool
reqDist *requestDistributor
retriever *retrieveManager
relay *lesTxRelay
bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests
bloomIndexer *core.ChainIndexer bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports
ApiBackend *LesApiBackend ApiBackend *LesApiBackend
eventMux *event.TypeMux eventMux *event.TypeMux
engine consensus.Engine engine consensus.Engine
accountManager *accounts.Manager accountManager *accounts.Manager
networkId uint64
netRPCService *ethapi.PublicNetAPI netRPCService *ethapi.PublicNetAPI
wg sync.WaitGroup
} }
func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { 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) log.Info("Initialised chain configuration", "config", chainConfig)
peers := newPeerSet() peers := newPeerSet()
quitSync := make(chan struct{})
leth := &LightEthereum{ leth := &LightEthereum{
lesCommons: lesCommons{ lesCommons: lesCommons{
chainDb: chainDb, genesis: genesisHash,
config: config, config: config,
iConfig: light.DefaultClientIndexerConfig,
},
chainConfig: chainConfig, chainConfig: chainConfig,
eventMux: ctx.EventMux, iConfig: light.DefaultClientIndexerConfig,
chainDb: chainDb,
peers: peers, peers: peers,
reqDist: newRequestDistributor(peers, quitSync, &mclock.System{}), closeCh: make(chan struct{}),
},
eventMux: ctx.EventMux,
reqDist: newRequestDistributor(peers, &mclock.System{}),
accountManager: ctx.AccountManager, accountManager: ctx.AccountManager,
engine: eth.CreateConsensusEngine(ctx, chainConfig, &config.Ethash, nil, false, chainDb), engine: eth.CreateConsensusEngine(ctx, chainConfig, &config.Ethash, nil, false, chainDb),
shutdownChan: make(chan bool),
networkId: config.NetworkId,
bloomRequests: make(chan chan *bloombits.Retrieval), bloomRequests: make(chan chan *bloombits.Retrieval),
bloomIndexer: eth.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations), 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.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool)
leth.relay = newLesTxRelay(peers, leth.retriever) 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 { if leth.blockchain, err = light.NewLightChain(leth.odr, leth.chainConfig, leth.engine); err != nil {
return nil, err 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 // Note: AddChildIndexer starts the update process for the child
leth.bloomIndexer.AddChildIndexer(leth.bloomTrieIndexer) leth.bloomIndexer.AddChildIndexer(leth.bloomTrieIndexer)
leth.chtIndexer.Start(leth.blockchain) leth.chtIndexer.Start(leth.blockchain)
leth.bloomIndexer.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. // Rewind the chain in case of an incompatible config upgrade.
if compat, ok := genesisErr.(*params.ConfigCompatError); ok { if compat, ok := genesisErr.(*params.ConfigCompatError); ok {
log.Warn("Rewinding chain to upgrade configuration", "err", compat) 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) rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig)
} }
leth.txPool = light.NewTxPool(leth.chainConfig, leth.blockchain, leth.relay)
leth.ApiBackend = &LesApiBackend{ctx.ExtRPCEnabled(), leth, nil} leth.ApiBackend = &LesApiBackend{ctx.ExtRPCEnabled(), leth, nil}
gpoParams := config.GPO gpoParams := config.GPO
if gpoParams.Default == nil { if gpoParams.Default == nil {
gpoParams.Default = config.Miner.GasPrice gpoParams.Default = config.Miner.GasPrice
} }
leth.ApiBackend.gpo = gasprice.NewOracle(leth.ApiBackend, gpoParams) 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 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{} type LightDummyAPI struct{}
// Etherbase is the address that mining rewards will be send to // Etherbase is the address that mining rewards will be send to
@ -206,7 +175,7 @@ func (s *LightEthereum) APIs() []rpc.API {
}, { }, {
Namespace: "eth", Namespace: "eth",
Version: "1.0", Version: "1.0",
Service: downloader.NewPublicDownloaderAPI(s.protocolManager.downloader, s.eventMux), Service: downloader.NewPublicDownloaderAPI(s.handler.downloader, s.eventMux),
Public: true, Public: true,
}, { }, {
Namespace: "eth", Namespace: "eth",
@ -221,7 +190,7 @@ func (s *LightEthereum) APIs() []rpc.API {
}, { }, {
Namespace: "les", Namespace: "les",
Version: "1.0", Version: "1.0",
Service: NewPrivateLightAPI(&s.lesCommons, s.protocolManager.reg), Service: NewPrivateLightAPI(&s.lesCommons),
Public: false, 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) TxPool() *light.TxPool { return s.txPool }
func (s *LightEthereum) Engine() consensus.Engine { return s.engine } func (s *LightEthereum) Engine() consensus.Engine { return s.engine }
func (s *LightEthereum) LesVersion() int { return int(ClientProtocolVersions[0]) } 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 } func (s *LightEthereum) EventMux() *event.TypeMux { return s.eventMux }
// Protocols implements node.Service, returning all the currently configured // Protocols implements node.Service, returning all the currently configured
// network protocols to start. // network protocols to start.
func (s *LightEthereum) Protocols() []p2p.Protocol { 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 // 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 { func (s *LightEthereum) Start(srvr *p2p.Server) error {
log.Warn("Light client mode is an experimental feature") log.Warn("Light client mode is an experimental feature")
// Start bloom request workers.
s.wg.Add(bloomServiceThreads)
s.startBloomHandlers(params.BloomBitsBlocksClient) 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 // clients are searching for the first advertised protocol in the list
protocolVersion := AdvertiseProtocolVersions[0] protocolVersion := AdvertiseProtocolVersions[0]
s.serverPool.start(srvr, lesTopic(s.blockchain.Genesis().Hash(), protocolVersion)) s.serverPool.start(srvr, lesTopic(s.blockchain.Genesis().Hash(), protocolVersion))
s.protocolManager.Start(s.config.LightPeers)
return nil return nil
} }
// Stop implements node.Service, terminating all internal goroutines used by the // Stop implements node.Service, terminating all internal goroutines used by the
// Ethereum protocol. // Ethereum protocol.
func (s *LightEthereum) Stop() error { func (s *LightEthereum) Stop() error {
close(s.closeCh)
s.peers.Close()
s.reqDist.close()
s.odr.Stop() s.odr.Stop()
s.relay.Stop() s.relay.Stop()
s.bloomIndexer.Close() s.bloomIndexer.Close()
s.chtIndexer.Close() s.chtIndexer.Close()
s.blockchain.Stop() s.blockchain.Stop()
s.protocolManager.Stop() s.handler.stop()
s.txPool.Stop() s.txPool.Stop()
s.engine.Close() s.engine.Close()
s.eventMux.Stop() s.eventMux.Stop()
s.serverPool.stop()
time.Sleep(time.Millisecond * 200)
s.chainDb.Close() s.chainDb.Close()
close(s.shutdownChan) s.wg.Wait()
log.Info("Light ethereum stopped")
return nil return nil
} }
// SetClient sets the rpc client and binds the registrar contract. // SetClient sets the rpc client and binds the registrar contract.
func (s *LightEthereum) SetContractBackend(backend bind.ContractBackend) { func (s *LightEthereum) SetContractBackend(backend bind.ContractBackend) {
// Short circuit if registrar is nil if s.registrar != nil {
if s.protocolManager.reg == nil { s.registrar.start(backend)
return
} }
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" "fmt"
"math/big" "math/big"
"sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "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/eth"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/p2p" "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/p2p/enode"
"github.com/ethereum/go-ethereum/params" "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. // lesCommons contains fields needed by both server and client.
type lesCommons struct { type lesCommons struct {
genesis common.Hash
config *eth.Config config *eth.Config
chainConfig *params.ChainConfig
iConfig *light.IndexerConfig iConfig *light.IndexerConfig
chainDb ethdb.Database chainDb ethdb.Database
protocolManager *ProtocolManager peers *peerSet
chainReader chainReader
chtIndexer, bloomTrieIndexer *core.ChainIndexer chtIndexer, bloomTrieIndexer *core.ChainIndexer
registrar *checkpointRegistrar
closeCh chan struct{}
wg sync.WaitGroup
} }
// NodeInfo represents a short summary of the Ethereum sub-protocol metadata // 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. // 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)) protos := make([]p2p.Protocol, len(versions))
for i, version := range versions { for i, version := range versions {
version := version version := version
@ -60,15 +91,10 @@ func (c *lesCommons) makeProtocols(versions []uint) []p2p.Protocol {
Version: version, Version: version,
Length: ProtocolLengths[version], Length: ProtocolLengths[version],
NodeInfo: c.nodeInfo, NodeInfo: c.nodeInfo,
Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { Run: func(peer *p2p.Peer, rw p2p.MsgReadWriter) error {
return c.protocolManager.runPeer(version, p, rw) return runPeer(version, peer, 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
}, },
PeerInfo: peerInfo,
} }
} }
return protos return protos
@ -76,15 +102,14 @@ func (c *lesCommons) makeProtocols(versions []uint) []p2p.Protocol {
// nodeInfo retrieves some protocol metadata about the running host node. // nodeInfo retrieves some protocol metadata about the running host node.
func (c *lesCommons) nodeInfo() interface{} { func (c *lesCommons) nodeInfo() interface{} {
chain := c.protocolManager.blockchain head := c.chainReader.CurrentHeader()
head := chain.CurrentHeader()
hash := head.Hash() hash := head.Hash()
return &NodeInfo{ return &NodeInfo{
Network: c.config.NetworkId, Network: c.config.NetworkId,
Difficulty: chain.GetTd(hash, head.Number.Uint64()), Difficulty: rawdb.ReadTd(c.chainDb, hash, head.Number.Uint64()),
Genesis: chain.Genesis().Hash(), Genesis: c.genesis,
Config: chain.Config(), Config: c.chainConfig,
Head: chain.CurrentHeader().Hash(), Head: hash,
CHT: c.latestLocalCheckpoint(), CHT: c.latestLocalCheckpoint(),
} }
} }
@ -103,15 +128,15 @@ func (c *lesCommons) latestLocalCheckpoint() params.TrustedCheckpoint {
// No checkpoint information can be provided. // No checkpoint information can be provided.
return params.TrustedCheckpoint{} 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. // associated with the appropriate head hash by specific section index.
// //
// The returned checkpoint is only the checkpoint generated by the local indexers, // The returned checkpoint is only the checkpoint generated by the local indexers,
// not the stable checkpoint registered in the registrar contract. // 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) sectionHead := c.chtIndexer.SectionHead(index)
return params.TrustedCheckpoint{ return params.TrustedCheckpoint{
SectionIndex: index, SectionIndex: index,

View file

@ -84,6 +84,7 @@ var (
const ( const (
maxCostFactor = 2 // ratio of maximum and average cost estimates maxCostFactor = 2 // ratio of maximum and average cost estimates
bufLimitRatio = 6000 // fixed bufLimit/MRR ratio
gfUsageThreshold = 0.5 gfUsageThreshold = 0.5
gfUsageTC = time.Second gfUsageTC = time.Second
gfRaiseTC = time.Second * 200 gfRaiseTC = time.Second * 200
@ -115,6 +116,10 @@ type costTracker struct {
stats map[uint64][]uint64 stats map[uint64][]uint64
logger *csvlogger.Logger logger *csvlogger.Logger
logRecentTime, logRecentAvg, logTotalRecharge, logRelCost *csvlogger.Channel 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. // newCostTracker creates a cost tracker and loads the cost factor statistics from the database.

View file

@ -33,9 +33,12 @@ type requestDistributor struct {
lastReqOrder uint64 lastReqOrder uint64
peers map[distPeer]struct{} peers map[distPeer]struct{}
peerLock sync.RWMutex peerLock sync.RWMutex
stopChn, loopChn chan struct{} loopChn chan struct{}
loopNextSent bool loopNextSent bool
lock sync.Mutex lock sync.Mutex
closeCh chan struct{}
wg sync.WaitGroup
} }
// distPeer is an LES server peer interface for the request distributor. // distPeer is an LES server peer interface for the request distributor.
@ -69,17 +72,18 @@ type distReq struct {
} }
// newRequestDistributor creates a new request distributor // 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{ d := &requestDistributor{
clock: clock, clock: clock,
reqQueue: list.New(), reqQueue: list.New(),
loopChn: make(chan struct{}, 2), loopChn: make(chan struct{}, 2),
stopChn: stopChn, closeCh: make(chan struct{}),
peers: make(map[distPeer]struct{}), peers: make(map[distPeer]struct{}),
} }
if peers != nil { if peers != nil {
peers.notify(d) peers.notify(d)
} }
d.wg.Add(1)
go d.loop() go d.loop()
return d return d
} }
@ -115,9 +119,10 @@ const waitForPeers = time.Second * 3
// main event loop // main event loop
func (d *requestDistributor) loop() { func (d *requestDistributor) loop() {
defer d.wg.Done()
for { for {
select { select {
case <-d.stopChn: case <-d.closeCh:
d.lock.Lock() d.lock.Lock()
elem := d.reqQueue.Front() elem := d.reqQueue.Front()
for elem != nil { for elem != nil {
@ -294,3 +299,8 @@ func (d *requestDistributor) remove(r *distReq) {
r.element = nil 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{}) stop := make(chan struct{})
defer close(stop) defer close(stop)
dist := newRequestDistributor(nil, stop, &mclock.System{}) dist := newRequestDistributor(nil, &mclock.System{})
var peers [testDistPeerCount]*testDistPeer var peers [testDistPeerCount]*testDistPeer
for i := range peers { for i := range peers {
peers[i] = &testDistPeer{} 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 // ODR system to ensure that we only request data related to a certain block from peers who have already processed
// and announced that block. // and announced that block.
type lightFetcher struct { type lightFetcher struct {
pm *ProtocolManager handler *clientHandler
odr *LesOdr chain *light.LightChain
chain lightChain
lock sync.Mutex // lock protects access to the fetcher's internal state variables except sent requests lock sync.Mutex // lock protects access to the fetcher's internal state variables except sent requests
maxConfirmedTd *big.Int maxConfirmedTd *big.Int
@ -58,13 +57,9 @@ type lightFetcher struct {
requestTriggered bool requestTriggered bool
requestTrigger chan struct{} requestTrigger chan struct{}
lastTrustedHeader *types.Header lastTrustedHeader *types.Header
}
// lightChain extends the BlockChain interface by locking. closeCh chan struct{}
type lightChain interface { wg sync.WaitGroup
BlockChain
LockChain()
UnlockChain()
} }
// fetcherPeerInfo holds fetcher-specific information about each active peer // fetcherPeerInfo holds fetcher-specific information about each active peer
@ -114,32 +109,36 @@ type fetchResponse struct {
} }
// newLightFetcher creates a new light fetcher // newLightFetcher creates a new light fetcher
func newLightFetcher(pm *ProtocolManager) *lightFetcher { func newLightFetcher(h *clientHandler) *lightFetcher {
f := &lightFetcher{ f := &lightFetcher{
pm: pm, handler: h,
chain: pm.blockchain.(*light.LightChain), chain: h.backend.blockchain,
odr: pm.odr,
peers: make(map[*peer]*fetcherPeerInfo), peers: make(map[*peer]*fetcherPeerInfo),
deliverChn: make(chan fetchResponse, 100), deliverChn: make(chan fetchResponse, 100),
requested: make(map[uint64]fetchRequest), requested: make(map[uint64]fetchRequest),
timeoutChn: make(chan uint64), timeoutChn: make(chan uint64),
requestTrigger: make(chan struct{}, 1), requestTrigger: make(chan struct{}, 1),
syncDone: make(chan *peer), syncDone: make(chan *peer),
closeCh: make(chan struct{}),
maxConfirmedTd: big.NewInt(0), 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() go f.syncLoop()
return f return f
} }
func (f *lightFetcher) close() {
close(f.closeCh)
}
// syncLoop is the main event loop of the light fetcher // syncLoop is the main event loop of the light fetcher
func (f *lightFetcher) syncLoop() { func (f *lightFetcher) syncLoop() {
defer f.pm.wg.Done() defer f.wg.Done()
for { for {
select { select {
case <-f.pm.quitSync: case <-f.closeCh:
return return
// request loop keeps running until no further requests are necessary or possible // request loop keeps running until no further requests are necessary or possible
case <-f.requestTrigger: case <-f.requestTrigger:
@ -156,7 +155,7 @@ func (f *lightFetcher) syncLoop() {
f.lock.Unlock() f.lock.Unlock()
if rq != nil { if rq != nil {
if _, ok := <-f.pm.reqDist.queue(rq); ok { if _, ok := <-f.handler.backend.reqDist.queue(rq); ok {
if syncing { if syncing {
f.lock.Lock() f.lock.Lock()
f.syncing = true f.syncing = true
@ -187,9 +186,9 @@ func (f *lightFetcher) syncLoop() {
} }
f.reqMu.Unlock() f.reqMu.Unlock()
if ok { 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") 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: case resp := <-f.deliverChn:
f.reqMu.Lock() f.reqMu.Lock()
@ -202,12 +201,12 @@ func (f *lightFetcher) syncLoop() {
} }
f.reqMu.Unlock() f.reqMu.Unlock()
if ok { 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() f.lock.Lock()
if !ok || !(f.syncing || f.processResponse(req, resp)) { if !ok || !(f.syncing || f.processResponse(req, resp)) {
resp.peer.Log().Debug("Failed processing response") resp.peer.Log().Debug("Failed processing response")
go f.pm.removePeer(resp.peer.id) go f.handler.removePeer(resp.peer.id)
} }
f.lock.Unlock() f.lock.Unlock()
case p := <-f.syncDone: 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 { if fp.lastAnnounced != nil && head.Td.Cmp(fp.lastAnnounced.td) <= 0 {
// announced tds should be strictly monotonic // announced tds should be strictly monotonic
p.Log().Debug("Received non-monotonic td", "current", head.Td, "previous", fp.lastAnnounced.td) 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 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 // if one of root's children is canonical, keep it, delete other branches and root itself
var newRoot *fetcherTreeNode var newRoot *fetcherTreeNode
for i, nn := range fp.root.children { 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:]...) fp.root.children = append(fp.root.children[:i], fp.root.children[i+1:]...)
nn.parent = nil nn.parent = nil
newRoot = nn 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 // 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 // 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 // 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 { if f.checkKnownNode(p, n) || n.requested {
continue continue
} }
// if ulc mode is disabled, isTrustedHash returns true // if ulc mode is disabled, isTrustedHash returns true
amount := f.requestAmount(p, n) 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 bestHash = hash
bestTd = n.td bestTd = n.td
bestAmount = amount 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. // isTrustedHash checks if the block can be trusted by the minimum trusted fraction.
func (f *lightFetcher) isTrustedHash(hash common.Hash) bool { func (f *lightFetcher) isTrustedHash(hash common.Hash) bool {
if !f.pm.isULCEnabled() { if !f.handler.isULCEnabled() {
return true return true
} }
var numAgreed int var numAgreed int
for p, fp := range f.peers { for p, fp := range f.peers {
if !p.isTrusted { if !p.trusted {
continue continue
} }
if _, ok := fp.nodeByHash[hash]; !ok { if _, ok := fp.nodeByHash[hash]; !ok {
continue continue
} }
numAgreed++ numAgreed++
} }
return 100*numAgreed/len(f.handler.ulc.trustedKeys) >= f.handler.ulc.minTrustedFraction
return 100*numAgreed/len(f.pm.ulc.trustedKeys) >= f.pm.ulc.minTrustedFraction
} }
func (f *lightFetcher) newFetcherDistReqForSync(bestHash common.Hash) *distReq { func (f *lightFetcher) newFetcherDistReqForSync(bestHash common.Hash) *distReq {
@ -498,7 +493,7 @@ func (f *lightFetcher) newFetcherDistReqForSync(bestHash common.Hash) *distReq {
f.lock.Lock() f.lock.Lock()
defer f.lock.Unlock() defer f.lock.Unlock()
if p.isOnlyAnnounce { if p.announceOnly {
return false return false
} }
@ -506,14 +501,14 @@ func (f *lightFetcher) newFetcherDistReqForSync(bestHash common.Hash) *distReq {
return fp != nil && fp.nodeByHash[bestHash] != nil return fp != nil && fp.nodeByHash[bestHash] != nil
}, },
request: func(dp distPeer) func() { request: func(dp distPeer) func() {
if f.pm.isULCEnabled() { if f.handler.isULCEnabled() {
//keep last trusted header before sync //keep last trusted header before sync
f.setLastTrustedHeader(f.chain.CurrentHeader()) f.setLastTrustedHeader(f.chain.CurrentHeader())
} }
go func() { go func() {
p := dp.(*peer) p := dp.(*peer)
p.Log().Debug("Synchronisation started") p.Log().Debug("Synchronisation started")
f.pm.synchronise(p) f.handler.synchronise(p)
f.syncDone <- p f.syncDone <- p
}() }()
return nil return nil
@ -533,7 +528,7 @@ func (f *lightFetcher) newFetcherDistReq(bestHash common.Hash, reqID uint64, bes
f.lock.Lock() f.lock.Lock()
defer f.lock.Unlock() defer f.lock.Unlock()
if p.isOnlyAnnounce { if p.announceOnly {
return false return false
} }
@ -614,7 +609,7 @@ func (f *lightFetcher) newHeaders(headers []*types.Header, tds []*big.Int) {
for p, fp := range f.peers { for p, fp := range f.peers {
if !f.checkAnnouncedHeaders(fp, headers, tds) { if !f.checkAnnouncedHeaders(fp, headers, tds) {
p.Log().Debug("Inconsistent announcement") 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) { if fp.confirmedTd != nil && (maxTd == nil || maxTd.Cmp(fp.confirmedTd) > 0) {
maxTd = fp.confirmedTd maxTd = fp.confirmedTd
@ -713,7 +708,7 @@ func (f *lightFetcher) checkSyncedHeaders(p *peer) {
var td *big.Int var td *big.Int
var h *types.Header var h *types.Header
if f.pm.isULCEnabled() { if f.handler.isULCEnabled() {
var unapprovedHashes []common.Hash var unapprovedHashes []common.Hash
// Overwrite last announced for ULC mode // Overwrite last announced for ULC mode
h, unapprovedHashes = f.lastTrustedTreeNode(p) 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 // Now n is the latest downloaded/approved header after syncing
if n == nil { if n == nil {
p.Log().Debug("Synchronisation failed") p.Log().Debug("Synchronisation failed")
go f.pm.removePeer(p.id) go f.handler.removePeer(p.id)
return return
} }
header := f.chain.GetHeader(n.hash, n.number) 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() { if canonical.Number.Uint64() > f.lastTrustedHeader.Number.Uint64() {
canonical = f.chain.GetHeaderByNumber(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 { 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()) 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 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}) { if !f.checkAnnouncedHeaders(fp, []*types.Header{header}, []*big.Int{td}) {
p.Log().Debug("Inconsistent announcement") p.Log().Debug("Inconsistent announcement")
go f.pm.removePeer(p.id) go f.handler.removePeer(p.id)
} }
if fp.confirmedTd != nil { if fp.confirmedTd != nil {
f.updateMaxConfirmedTd(fp.confirmedTd) f.updateMaxConfirmedTd(fp.confirmedTd)
@ -893,12 +888,12 @@ func (f *lightFetcher) checkUpdateStats(p *peer, newEntry *updateStatsEntry) {
fp.firstUpdateStats = newEntry fp.firstUpdateStats = newEntry
} }
for fp.firstUpdateStats != nil && fp.firstUpdateStats.time <= now-mclock.AbsTime(blockDelayTimeout) { 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 fp.firstUpdateStats = fp.firstUpdateStats.next
} }
if fp.confirmedTd != nil { if fp.confirmedTd != nil {
for fp.firstUpdateStats != nil && fp.firstUpdateStats.td.Cmp(fp.confirmedTd) <= 0 { 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 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 // 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{ pool := &freeClientPool{
db: db, db: db,
clock: clock, 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. // Tests that block headers can be retrieved from a remote chain based on user queries.
func TestGetBlockHeadersLes2(t *testing.T) { testGetBlockHeaders(t, 2) } func TestGetBlockHeadersLes2(t *testing.T) { testGetBlockHeaders(t, 2) }
func TestGetBlockHeadersLes3(t *testing.T) { testGetBlockHeaders(t, 3) }
func testGetBlockHeaders(t *testing.T, protocol int) { 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() defer tearDown()
bc := server.pm.blockchain.(*core.BlockChain)
bc := server.handler.blockchain
// Create a "random" unknown hash for testing // Create a "random" unknown hash for testing
var unknown common.Hash var unknown common.Hash
@ -114,10 +116,10 @@ func testGetBlockHeaders(t *testing.T, protocol int) {
[]common.Hash{bc.CurrentBlock().Hash()}, []common.Hash{bc.CurrentBlock().Hash()},
}, },
// Ensure protocol limits are honored // Ensure protocol limits are honored
/*{ //{
&getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 1}, Amount: limit + 10, Reverse: true}, // &getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 1}, Amount: limit + 10, Reverse: true},
bc.GetBlockHashesFromHash(bc.CurrentBlock().Hash(), limit), // []common.Hash{},
},*/ //},
// Check that requesting more than available is handled gracefully // Check that requesting more than available is handled gracefully
{ {
&getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 4}, Skip: 3, Amount: 3}, &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 // Send the hash request and verify the response
reqID++ reqID++
cost := server.tPeer.GetRequestCost(GetBlockHeadersMsg, int(tt.query.Amount))
sendRequest(server.tPeer.app, GetBlockHeadersMsg, reqID, cost, tt.query) cost := server.peer.peer.GetRequestCost(GetBlockHeadersMsg, int(tt.query.Amount))
if err := expectResponse(server.tPeer.app, BlockHeadersMsg, reqID, testBufLimit, headers); err != nil { 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) 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. // Tests that block contents can be retrieved from a remote chain based on their hashes.
func TestGetBlockBodiesLes2(t *testing.T) { testGetBlockBodies(t, 2) } func TestGetBlockBodiesLes2(t *testing.T) { testGetBlockBodies(t, 2) }
func TestGetBlockBodiesLes3(t *testing.T) { testGetBlockBodies(t, 3) }
func testGetBlockBodies(t *testing.T, protocol int) { 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() defer tearDown()
bc := server.pm.blockchain.(*core.BlockChain)
bc := server.handler.blockchain
// Create a batch of tests for various scenarios // Create a batch of tests for various scenarios
limit := MaxBodyFetch limit := MaxBodyFetch
@ -238,10 +243,11 @@ func testGetBlockBodies(t *testing.T, protocol int) {
} }
} }
reqID++ reqID++
// Send the hash request and verify the response // Send the hash request and verify the response
cost := server.tPeer.GetRequestCost(GetBlockBodiesMsg, len(hashes)) cost := server.peer.peer.GetRequestCost(GetBlockBodiesMsg, len(hashes))
sendRequest(server.tPeer.app, GetBlockBodiesMsg, reqID, cost, hashes) sendRequest(server.peer.app, GetBlockBodiesMsg, reqID, cost, hashes)
if err := expectResponse(server.tPeer.app, BlockBodiesMsg, reqID, testBufLimit, bodies); err != nil { if err := expectResponse(server.peer.app, BlockBodiesMsg, reqID, testBufLimit, bodies); err != nil {
t.Errorf("test %d: bodies mismatch: %v", i, err) 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. // Tests that the contract codes can be retrieved based on account addresses.
func TestGetCodeLes2(t *testing.T) { testGetCode(t, 2) } func TestGetCodeLes2(t *testing.T) { testGetCode(t, 2) }
func TestGetCodeLes3(t *testing.T) { testGetCode(t, 3) }
func testGetCode(t *testing.T, protocol int) { func testGetCode(t *testing.T, protocol int) {
// Assemble the test environment // Assemble the test environment
server, tearDown := newServerEnv(t, 4, protocol, nil) server, tearDown := newServerEnv(t, 4, protocol, nil, false, true, 0)
defer tearDown() defer tearDown()
bc := server.pm.blockchain.(*core.BlockChain) bc := server.handler.blockchain
var codereqs []*CodeReq var codereqs []*CodeReq
var codes [][]byte var codes [][]byte
@ -270,21 +277,23 @@ func testGetCode(t *testing.T, protocol int) {
} }
} }
cost := server.tPeer.GetRequestCost(GetCodeMsg, len(codereqs)) cost := server.peer.peer.GetRequestCost(GetCodeMsg, len(codereqs))
sendRequest(server.tPeer.app, GetCodeMsg, 42, cost, codereqs) sendRequest(server.peer.app, GetCodeMsg, 42, cost, codereqs)
if err := expectResponse(server.tPeer.app, CodeMsg, 42, testBufLimit, codes); err != nil { if err := expectResponse(server.peer.app, CodeMsg, 42, testBufLimit, codes); err != nil {
t.Errorf("codes mismatch: %v", err) t.Errorf("codes mismatch: %v", err)
} }
} }
// Tests that the transaction receipts can be retrieved based on hashes. // Tests that the transaction receipts can be retrieved based on hashes.
func TestGetReceiptLes2(t *testing.T) { testGetReceipt(t, 2) } func TestGetReceiptLes2(t *testing.T) { testGetReceipt(t, 2) }
func TestGetReceiptLes3(t *testing.T) { testGetReceipt(t, 3) }
func testGetReceipt(t *testing.T, protocol int) { func testGetReceipt(t *testing.T, protocol int) {
// Assemble the test environment // Assemble the test environment
server, tearDown := newServerEnv(t, 4, protocol, nil) server, tearDown := newServerEnv(t, 4, protocol, nil, false, true, 0)
defer tearDown() defer tearDown()
bc := server.pm.blockchain.(*core.BlockChain)
bc := server.handler.blockchain
// Collect the hashes to request, and the response to expect // Collect the hashes to request, and the response to expect
hashes, receipts := []common.Hash{}, []types.Receipts{} 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())) receipts = append(receipts, rawdb.ReadRawReceipts(server.db, block.Hash(), block.NumberU64()))
} }
// Send the hash request and verify the response // Send the hash request and verify the response
cost := server.tPeer.GetRequestCost(GetReceiptsMsg, len(hashes)) cost := server.peer.peer.GetRequestCost(GetReceiptsMsg, len(hashes))
sendRequest(server.tPeer.app, GetReceiptsMsg, 42, cost, hashes) sendRequest(server.peer.app, GetReceiptsMsg, 42, cost, hashes)
if err := expectResponse(server.tPeer.app, ReceiptsMsg, 42, testBufLimit, receipts); err != nil { if err := expectResponse(server.peer.app, ReceiptsMsg, 42, testBufLimit, receipts); err != nil {
t.Errorf("receipts mismatch: %v", err) t.Errorf("receipts mismatch: %v", err)
} }
} }
// Tests that trie merkle proofs can be retrieved // Tests that trie merkle proofs can be retrieved
func TestGetProofsLes2(t *testing.T) { testGetProofs(t, 2) } func TestGetProofsLes2(t *testing.T) { testGetProofs(t, 2) }
func TestGetProofsLes3(t *testing.T) { testGetProofs(t, 3) }
func testGetProofs(t *testing.T, protocol int) { func testGetProofs(t *testing.T, protocol int) {
// Assemble the test environment // Assemble the test environment
server, tearDown := newServerEnv(t, 4, protocol, nil) server, tearDown := newServerEnv(t, 4, protocol, nil, false, true, 0)
defer tearDown() defer tearDown()
bc := server.pm.blockchain.(*core.BlockChain)
bc := server.handler.blockchain
var proofreqs []ProofReq var proofreqs []ProofReq
proofsV2 := light.NewNodeSet() 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++ { for i := uint64(0); i <= bc.CurrentBlock().NumberU64(); i++ {
header := bc.GetHeaderByNumber(i) header := bc.GetHeaderByNumber(i)
trie, _ := trie.New(header.Root, trie.NewDatabase(server.db)) 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 // Send the proof request and verify the response
cost := server.tPeer.GetRequestCost(GetProofsV2Msg, len(proofreqs)) cost := server.peer.peer.GetRequestCost(GetProofsV2Msg, len(proofreqs))
sendRequest(server.tPeer.app, GetProofsV2Msg, 42, cost, proofreqs) sendRequest(server.peer.app, GetProofsV2Msg, 42, cost, proofreqs)
if err := expectResponse(server.tPeer.app, ProofsV2Msg, 42, testBufLimit, proofsV2.NodeList()); err != nil { if err := expectResponse(server.peer.app, ProofsV2Msg, 42, testBufLimit, proofsV2.NodeList()); err != nil {
t.Errorf("proofs mismatch: %v", err) t.Errorf("proofs mismatch: %v", err)
} }
} }
// Tests that CHT proofs can be correctly retrieved. // Tests that CHT proofs can be correctly retrieved.
func TestGetCHTProofsLes2(t *testing.T) { testGetCHTProofs(t, 2) } func TestGetCHTProofsLes2(t *testing.T) { testGetCHTProofs(t, 2) }
func TestGetCHTProofsLes3(t *testing.T) { testGetCHTProofs(t, 3) }
func testGetCHTProofs(t *testing.T, protocol int) { func testGetCHTProofs(t *testing.T, protocol int) {
config := light.TestServerIndexerConfig config := light.TestServerIndexerConfig
@ -351,9 +363,10 @@ func testGetCHTProofs(t *testing.T, protocol int) {
time.Sleep(10 * time.Millisecond) 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() defer tearDown()
bc := server.pm.blockchain.(*core.BlockChain)
bc := server.handler.blockchain
// Assemble the proofs from the different protocols // Assemble the proofs from the different protocols
header := bc.GetHeaderByNumber(config.ChtSize - 1) header := bc.GetHeaderByNumber(config.ChtSize - 1)
@ -376,15 +389,18 @@ func testGetCHTProofs(t *testing.T, protocol int) {
AuxReq: auxHeader, AuxReq: auxHeader,
}} }}
// Send the proof request and verify the response // Send the proof request and verify the response
cost := server.tPeer.GetRequestCost(GetHelperTrieProofsMsg, len(requestsV2)) cost := server.peer.peer.GetRequestCost(GetHelperTrieProofsMsg, len(requestsV2))
sendRequest(server.tPeer.app, GetHelperTrieProofsMsg, 42, cost, requestsV2) sendRequest(server.peer.app, GetHelperTrieProofsMsg, 42, cost, requestsV2)
if err := expectResponse(server.tPeer.app, HelperTrieProofsMsg, 42, testBufLimit, proofsV2); err != nil { if err := expectResponse(server.peer.app, HelperTrieProofsMsg, 42, testBufLimit, proofsV2); err != nil {
t.Errorf("proofs mismatch: %v", err) 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. // Tests that bloombits proofs can be correctly retrieved.
func TestGetBloombitsProofs(t *testing.T) { func testGetBloombitsProofs(t *testing.T, protocol int) {
config := light.TestServerIndexerConfig config := light.TestServerIndexerConfig
waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) {
@ -396,9 +412,10 @@ func TestGetBloombitsProofs(t *testing.T) {
time.Sleep(10 * time.Millisecond) 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() defer tearDown()
bc := server.pm.blockchain.(*core.BlockChain)
bc := server.handler.blockchain
// Request and verify each bit of the bloom bits proofs // Request and verify each bit of the bloom bits proofs
for bit := 0; bit < 2048; bit++ { for bit := 0; bit < 2048; bit++ {
@ -421,42 +438,38 @@ func TestGetBloombitsProofs(t *testing.T) {
trie.Prove(key, 0, &proofs.Proofs) trie.Prove(key, 0, &proofs.Proofs)
// Send the proof request and verify the response // Send the proof request and verify the response
cost := server.tPeer.GetRequestCost(GetHelperTrieProofsMsg, len(requests)) cost := server.peer.peer.GetRequestCost(GetHelperTrieProofsMsg, len(requests))
sendRequest(server.tPeer.app, GetHelperTrieProofsMsg, 42, cost, requests) sendRequest(server.peer.app, GetHelperTrieProofsMsg, 42, cost, requests)
if err := expectResponse(server.tPeer.app, HelperTrieProofsMsg, 42, testBufLimit, proofs); err != nil { if err := expectResponse(server.peer.app, HelperTrieProofsMsg, 42, testBufLimit, proofs); err != nil {
t.Errorf("bit %d: proofs mismatch: %v", bit, err) t.Errorf("bit %d: proofs mismatch: %v", bit, err)
} }
} }
} }
func TestTransactionStatusLes2(t *testing.T) { func TestTransactionStatusLes2(t *testing.T) { testTransactionStatus(t, 2) }
server, tearDown := newServerEnv(t, 0, 2, nil) 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() defer tearDown()
chain := server.pm.blockchain.(*core.BlockChain) chain := server.handler.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()
var reqID uint64 var reqID uint64
test := func(tx *types.Transaction, send bool, expStatus light.TxStatus) { test := func(tx *types.Transaction, send bool, expStatus light.TxStatus) {
reqID++ reqID++
if send { if send {
cost := server.tPeer.GetRequestCost(SendTxV2Msg, 1) cost := server.peer.peer.GetRequestCost(SendTxV2Msg, 1)
sendRequest(server.tPeer.app, SendTxV2Msg, reqID, cost, types.Transactions{tx}) sendRequest(server.peer.app, SendTxV2Msg, reqID, cost, types.Transactions{tx})
} else { } else {
cost := server.tPeer.GetRequestCost(GetTxStatusMsg, 1) cost := server.peer.peer.GetRequestCost(GetTxStatusMsg, 1)
sendRequest(server.tPeer.app, GetTxStatusMsg, reqID, cost, []common.Hash{tx.Hash()}) 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") t.Errorf("transaction status mismatch")
} }
} }
signer := types.HomesteadSigner{} signer := types.HomesteadSigner{}
// test error status by sending an underpriced transaction // test error status by sending an underpriced transaction
@ -486,18 +499,22 @@ func TestTransactionStatusLes2(t *testing.T) {
} }
// wait until TxPool processes the inserted block // wait until TxPool processes the inserted block
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
if pending, _ := txpool.Stats(); pending == 1 { if pending, _ := server.handler.txpool.Stats(); pending == 1 {
break break
} }
time.Sleep(100 * time.Millisecond) 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) 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 // check if their status is included now
block1hash := rawdb.ReadCanonicalHash(server.db, 1) block1hash := rawdb.ReadCanonicalHash(server.db, 1)
test(tx1, false, light.TxStatus{Status: core.TxStatusIncluded, Lookup: &rawdb.LegacyTxLookupEntry{BlockHash: block1hash, BlockIndex: 1, Index: 0}}) 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}}) test(tx2, false, light.TxStatus{Status: core.TxStatusIncluded, Lookup: &rawdb.LegacyTxLookupEntry{BlockHash: block1hash, BlockIndex: 1, Index: 1}})
// create a reorg that rolls them back // create a reorg that rolls them back
@ -507,44 +524,44 @@ func TestTransactionStatusLes2(t *testing.T) {
} }
// wait until TxPool processes the reorg // wait until TxPool processes the reorg
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
if pending, _ := txpool.Stats(); pending == 3 { if pending, _ := server.handler.txpool.Stats(); pending == 3 {
break break
} }
time.Sleep(100 * time.Millisecond) 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) 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 // check if their status is pending again
test(tx1, false, light.TxStatus{Status: core.TxStatusPending}) test(tx1, false, light.TxStatus{Status: core.TxStatusPending})
test(tx2, false, light.TxStatus{Status: core.TxStatusPending}) test(tx2, false, light.TxStatus{Status: core.TxStatusPending})
} }
func TestStopResumeLes3(t *testing.T) { func TestStopResumeLes3(t *testing.T) {
db := rawdb.NewMemoryDatabase() server, tearDown := newServerEnv(t, 0, 3, nil, true, true, testBufLimit/10)
clock := &mclock.Simulated{} defer tearDown()
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()
expBuf := testBufLimit server.handler.server.costTracker.disableRealCost = true
var reqID uint64
var (
reqID uint64
expBuf = testBufLimit
testCost = testBufLimit / 10
)
req := func() { req := func() {
reqID++ 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++ { for i := 1; i <= 5; i++ {
// send requests while we still have enough buffer and expect a response // send requests while we still have enough buffer and expect a response
for expBuf >= testCost { for expBuf >= testCost {
req() req()
expBuf -= testCost 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) t.Errorf("expected response and failed: %v", err)
} }
} }
@ -554,15 +571,16 @@ func TestStopResumeLes3(t *testing.T) {
req() req()
c-- 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) t.Errorf("expected StopMsg and failed: %v", err)
} }
// wait until the buffer is recharged by half of the limit // wait until the buffer is recharged by half of the limit
wait := testBufLimit / testBufRecharge / 2 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 // expect a ResumeMsg with the partially recharged buffer value
expBuf += testBufRecharge * wait 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) 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 { canSend: func(dp distPeer) bool {
p := dp.(*peer) p := dp.(*peer)
if !p.isOnlyAnnounce { if !p.announceOnly {
return lreq.CanSend(p) return lreq.CanSend(p)
} }
return false 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 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 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 { 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 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 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 { 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 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 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 { 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") 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 TestOdrContractCallLes2(t *testing.T) { testOdr(t, 2, 2, true, odrContractCall) }
func TestOdrContractCallLes3(t *testing.T) { testOdr(t, 3, 2, true, odrContractCall) }
type callmsg struct { type callmsg struct {
types.Message 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 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 { 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 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. // testOdr tests odr requests whose validation guaranteed by block headers.
func testOdr(t *testing.T, protocol int, expFail uint64, checkCached bool, fn odrTestFn) { func testOdr(t *testing.T, protocol int, expFail uint64, checkCached bool, fn odrTestFn) {
// Assemble the test environment // 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() defer tearDown()
client.pm.synchronise(client.rPeer)
client.handler.synchronise(client.peer.peer)
test := func(expFail uint64) { test := func(expFail uint64) {
// Mark this as a helper to put the failures at the correct lines // Mark this as a helper to put the failures at the correct lines
t.Helper() 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) 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) ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
defer cancel() b2 := fn(ctx, client.db, client.handler.backend.chainConfig, nil, client.handler.backend.blockchain, bhash)
b2 := fn(ctx, client.db, client.pm.chainConfig, nil, client.pm.blockchain.(*light.LightChain), bhash) cancel()
eq := bytes.Equal(b1, b2) eq := bytes.Equal(b1, b2)
exp := i < expFail 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 // expect retrievals to fail (except genesis block) without a les peer
client.peers.Unregister(client.rPeer.id) client.handler.backend.peers.lock.Lock()
time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed client.peer.peer.hasBlock = func(common.Hash, uint64, bool) bool { return false }
client.handler.backend.peers.lock.Unlock()
test(expFail) test(expFail)
// expect all retrievals to pass // expect all retrievals to pass
client.peers.Register(client.rPeer) client.handler.backend.peers.lock.Lock()
time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed client.peer.peer.hasBlock = func(common.Hash, uint64, bool) bool { return true }
client.peers.lock.Lock() client.handler.backend.peers.lock.Unlock()
client.rPeer.hasBlock = func(common.Hash, uint64, bool) bool { return true }
client.peers.lock.Unlock()
test(5) test(5)
if checkCached {
// still expect all retrievals to pass, now data should be cached locally // still expect all retrievals to pass, now data should be cached locally
client.peers.Unregister(client.rPeer.id) if checkCached {
client.handler.backend.peers.Unregister(client.peer.peer.id)
time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed
test(5) test(5)
} }

View file

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

View file

@ -1,28 +1,54 @@
// 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 package les
import ( import (
"math/big" "math/big"
"net"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/core/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/les/flowcontrol"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
const ( const protocolVersion = lpv2
test_networkid = 10
protocol_version = lpv2
)
var ( var (
hash = common.HexToHash("some string") hash = common.HexToHash("deadbeef")
genesis = common.HexToHash("genesis hash") genesis = common.HexToHash("cafebabe")
headNum = uint64(1234) headNum = uint64(1234)
td = big.NewInt(123) td = big.NewInt(123)
) )
func newNodeID(t *testing.T) *enode.Node {
key, err := crypto.GenerateKey()
if err != nil {
t.Fatal("generate key err:", err)
}
return enode.NewV4(&key.PublicKey, net.IP{}, 35000, 35000)
}
// ulc connects to trusted peer and send announceType=announceTypeSigned // ulc connects to trusted peer and send announceType=announceTypeSigned
func TestPeerHandshakeSetAnnounceTypeToAnnounceTypeSignedForTrustedPeer(t *testing.T) { func TestPeerHandshakeSetAnnounceTypeToAnnounceTypeSignedForTrustedPeer(t *testing.T) {
id := newNodeID(t).ID() id := newNodeID(t).ID()
@ -30,19 +56,16 @@ func TestPeerHandshakeSetAnnounceTypeToAnnounceTypeSignedForTrustedPeer(t *testi
// peer to connect(on ulc side) // peer to connect(on ulc side)
p := peer{ p := peer{
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}), Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
version: protocol_version, version: protocolVersion,
isTrusted: true, trusted: true,
rw: &rwStub{ rw: &rwStub{
WriteHook: func(recvList keyValueList) { WriteHook: func(recvList keyValueList) {
//checking that ulc sends to peer allowedRequests=onlyAnnounceRequests and announceType = announceTypeSigned
recv, _ := recvList.decode() recv, _ := recvList.decode()
var reqType uint64 var reqType uint64
err := recv.get("announceType", &reqType) err := recv.get("announceType", &reqType)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if reqType != announceTypeSigned { if reqType != announceTypeSigned {
t.Fatal("Expected announceTypeSigned") t.Fatal("Expected announceTypeSigned")
} }
@ -55,18 +78,15 @@ func TestPeerHandshakeSetAnnounceTypeToAnnounceTypeSignedForTrustedPeer(t *testi
l = l.add("flowControl/BL", uint64(0)) l = l.add("flowControl/BL", uint64(0))
l = l.add("flowControl/MRR", uint64(0)) l = l.add("flowControl/MRR", uint64(0))
l = l.add("flowControl/MRC", testCostList(0)) l = l.add("flowControl/MRC", testCostList(0))
return l return l
}, },
}, },
network: test_networkid, network: NetworkId,
} }
err := p.Handshake(td, hash, headNum, genesis, nil) err := p.Handshake(td, hash, headNum, genesis, nil)
if err != nil { if err != nil {
t.Fatalf("Handshake error: %s", err) t.Fatalf("Handshake error: %s", err)
} }
if p.announceType != announceTypeSigned { if p.announceType != announceTypeSigned {
t.Fatal("Incorrect announceType") t.Fatal("Incorrect announceType")
} }
@ -76,18 +96,16 @@ func TestPeerHandshakeAnnounceTypeSignedForTrustedPeersPeerNotInTrusted(t *testi
id := newNodeID(t).ID() id := newNodeID(t).ID()
p := peer{ p := peer{
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}), Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
version: protocol_version, version: protocolVersion,
rw: &rwStub{ rw: &rwStub{
WriteHook: func(recvList keyValueList) { 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() recv, _ := recvList.decode()
var reqType uint64 var reqType uint64
err := recv.get("announceType", &reqType) err := recv.get("announceType", &reqType)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if reqType == announceTypeSigned { if reqType == announceTypeSigned {
t.Fatal("Expected not 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/BL", uint64(0))
l = l.add("flowControl/MRR", uint64(0)) l = l.add("flowControl/MRR", uint64(0))
l = l.add("flowControl/MRC", testCostList(0)) l = l.add("flowControl/MRC", testCostList(0))
return l return l
}, },
}, },
network: test_networkid, network: NetworkId,
} }
err := p.Handshake(td, hash, headNum, genesis, nil) err := p.Handshake(td, hash, headNum, genesis, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -123,24 +139,22 @@ func TestPeerHandshakeDefaultAllRequests(t *testing.T) {
p := peer{ p := peer{
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}), Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
version: protocol_version, version: protocolVersion,
rw: &rwStub{ rw: &rwStub{
ReadHook: func(l keyValueList) keyValueList { ReadHook: func(l keyValueList) keyValueList {
l = l.add("announceType", uint64(announceTypeSigned)) l = l.add("announceType", uint64(announceTypeSigned))
l = l.add("allowedRequests", uint64(0)) l = l.add("allowedRequests", uint64(0))
return l return l
}, },
}, },
network: test_networkid, network: NetworkId,
} }
err := p.Handshake(td, hash, headNum, genesis, s) err := p.Handshake(td, hash, headNum, genesis, s)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if p.announceOnly {
if p.isOnlyAnnounce {
t.Fatal("Incorrect announceType") t.Fatal("Incorrect announceType")
} }
} }
@ -149,15 +163,14 @@ func TestPeerHandshakeServerSendOnlyAnnounceRequestsHeaders(t *testing.T) {
id := newNodeID(t).ID() id := newNodeID(t).ID()
s := generateLesServer() s := generateLesServer()
s.onlyAnnounce = true s.config.OnlyAnnounce = true
p := peer{ p := peer{
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}), Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
version: protocol_version, version: protocolVersion,
rw: &rwStub{ rw: &rwStub{
ReadHook: func(l keyValueList) keyValueList { ReadHook: func(l keyValueList) keyValueList {
l = l.add("announceType", uint64(announceTypeSigned)) l = l.add("announceType", uint64(announceTypeSigned))
return l return l
}, },
WriteHook: func(l keyValueList) { 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) err := p.Handshake(td, hash, headNum, genesis, s)
@ -184,7 +197,7 @@ func TestPeerHandshakeClientReceiveOnlyAnnounceRequestsHeaders(t *testing.T) {
p := peer{ p := peer{
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}), Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
version: protocol_version, version: protocolVersion,
rw: &rwStub{ rw: &rwStub{
ReadHook: func(l keyValueList) keyValueList { ReadHook: func(l keyValueList) keyValueList {
l = l.add("flowControl/BL", uint64(0)) l = l.add("flowControl/BL", uint64(0))
@ -196,8 +209,8 @@ func TestPeerHandshakeClientReceiveOnlyAnnounceRequestsHeaders(t *testing.T) {
return l return l
}, },
}, },
network: test_networkid, network: NetworkId,
isTrusted: true, trusted: true,
} }
err := p.Handshake(td, hash, headNum, genesis, nil) err := p.Handshake(td, hash, headNum, genesis, nil)
@ -205,7 +218,7 @@ func TestPeerHandshakeClientReceiveOnlyAnnounceRequestsHeaders(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if !p.isOnlyAnnounce { if !p.announceOnly {
t.Fatal("isOnlyAnnounce must be true") t.Fatal("isOnlyAnnounce must be true")
} }
} }
@ -215,19 +228,17 @@ func TestPeerHandshakeClientReturnErrorOnUselessPeer(t *testing.T) {
p := peer{ p := peer{
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}), Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
version: protocol_version, version: protocolVersion,
rw: &rwStub{ rw: &rwStub{
ReadHook: func(l keyValueList) keyValueList { ReadHook: func(l keyValueList) keyValueList {
l = l.add("flowControl/BL", uint64(0)) l = l.add("flowControl/BL", uint64(0))
l = l.add("flowControl/MRR", uint64(0)) l = l.add("flowControl/MRR", uint64(0))
l = l.add("flowControl/MRC", RequestCostList{}) l = l.add("flowControl/MRC", RequestCostList{})
l = l.add("announceType", uint64(announceTypeSigned)) l = l.add("announceType", uint64(announceTypeSigned))
return l return l
}, },
}, },
network: test_networkid, network: NetworkId,
} }
err := p.Handshake(td, hash, headNum, genesis, nil) err := p.Handshake(td, hash, headNum, genesis, nil)
@ -238,12 +249,16 @@ func TestPeerHandshakeClientReturnErrorOnUselessPeer(t *testing.T) {
func generateLesServer() *LesServer { func generateLesServer() *LesServer {
s := &LesServer{ s := &LesServer{
lesCommons: lesCommons{
config: &eth.Config{OnlyAnnounce: true},
},
defParams: flowcontrol.ServerParams{ defParams: flowcontrol.ServerParams{
BufLimit: uint64(300000000), BufLimit: uint64(300000000),
MinRecharge: uint64(50000), MinRecharge: uint64(50000),
}, },
fcManager: flowcontrol.NewClientManager(nil, &mclock.System{}), fcManager: flowcontrol.NewClientManager(nil, &mclock.System{}),
} }
s.costTracker, _ = newCostTracker(rawdb.NewMemoryDatabase(), s.config, nil)
return s return s
} }
@ -254,8 +269,8 @@ type rwStub struct {
func (s *rwStub) ReadMsg() (p2p.Msg, error) { func (s *rwStub) ReadMsg() (p2p.Msg, error) {
payload := keyValueList{} payload := keyValueList{}
payload = payload.add("protocolVersion", uint64(protocol_version)) payload = payload.add("protocolVersion", uint64(protocolVersion))
payload = payload.add("networkId", uint64(test_networkid)) payload = payload.add("networkId", uint64(NetworkId))
payload = payload.add("headTd", td) payload = payload.add("headTd", td)
payload = payload.add("headHash", hash) payload = payload.add("headHash", hash)
payload = payload.add("headNum", headNum) payload = payload.add("headNum", headNum)
@ -264,12 +279,10 @@ func (s *rwStub) ReadMsg() (p2p.Msg, error) {
if s.ReadHook != nil { if s.ReadHook != nil {
payload = s.ReadHook(payload) payload = s.ReadHook(payload)
} }
size, p, err := rlp.EncodeToReader(payload) size, p, err := rlp.EncodeToReader(payload)
if err != nil { if err != nil {
return p2p.Msg{}, err return p2p.Msg{}, err
} }
return p2p.Msg{ return p2p.Msg{
Size: uint32(size), Size: uint32(size),
Payload: p, Payload: p,
@ -281,10 +294,8 @@ func (s *rwStub) WriteMsg(m p2p.Msg) error {
if err := m.Decode(&recvList); err != nil { if err := m.Decode(&recvList); err != nil {
return err return err
} }
if s.WriteHook != nil { if s.WriteHook != nil {
s.WriteHook(recvList) s.WriteHook(recvList)
} }
return nil 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 type accessTestFn func(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest
func TestBlockAccessLes2(t *testing.T) { testAccess(t, 2, tfBlockAccess) } 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 { func tfBlockAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest {
return &light.BlockRequest{Hash: bhash, Number: number} return &light.BlockRequest{Hash: bhash, Number: number}
} }
func TestReceiptsAccessLes2(t *testing.T) { testAccess(t, 2, tfReceiptsAccess) } 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 { func tfReceiptsAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest {
return &light.ReceiptsRequest{Hash: bhash, Number: number} return &light.ReceiptsRequest{Hash: bhash, Number: number}
} }
func TestTrieEntryAccessLes2(t *testing.T) { testAccess(t, 2, tfTrieEntryAccess) } 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 { func tfTrieEntryAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest {
if number := rawdb.ReadHeaderNumber(db, bhash); number != nil { 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 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 { func tfCodeAccess(db ethdb.Database, bhash common.Hash, num uint64) light.OdrRequest {
number := rawdb.ReadHeaderNumber(db, bhash) 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) { func testAccess(t *testing.T, protocol int, fn accessTestFn) {
// Assemble the test environment // 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() defer tearDown()
client.pm.synchronise(client.rPeer) client.handler.synchronise(client.peer.peer)
test := func(expFail uint64) { 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) bhash := rawdb.ReadCanonicalHash(server.db, i)
if req := fn(client.db, bhash, i); req != nil { if req := fn(client.db, bhash, i); req != nil {
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
defer cancel() err := client.handler.backend.odr.Retrieve(ctx, req)
err := client.pm.odr.Retrieve(ctx, req) cancel()
got := err == nil got := err == nil
exp := i < expFail exp := i < expFail
if exp && !got { 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) test(5)
} }

View file

@ -18,15 +18,12 @@ package les
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"sync" "fmt"
"time" "time"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "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/common/mclock"
"github.com/ethereum/go-ethereum/core" "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/eth"
"github.com/ethereum/go-ethereum/les/csvlogger" "github.com/ethereum/go-ethereum/les/csvlogger"
"github.com/ethereum/go-ethereum/les/flowcontrol" "github.com/ethereum/go-ethereum/les/flowcontrol"
@ -34,101 +31,124 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discv5" "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/params"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
const bufLimitRatio = 6000 // fixed bufLimit/MRR ratio
const ( const (
logFileName = "" // csv log file name (disabled if empty) logFileName = "" // csv log file name (disabled if empty)
logClientPoolMetrics = true // log client pool metrics logClientPoolMetrics = true // log client pool metrics
logClientPoolEvents = false // detailed client pool event logging logClientPoolEvents = false // detailed client pool event logging
logRequestServing = true // log request serving metrics and events logRequestServing = true // log request serving metrics and events
logBlockProcEvents = true // log block processing events logBlockProcEvents = true // log block processing events
logProtocolHandler = true // log protocol handler events logHandlerEvents = true // log protocol handler events
) )
type LesServer struct { type LesServer struct {
lesCommons lesCommons
fcManager *flowcontrol.ClientManager // nil if our node is client only handler *serverHandler
costTracker *costTracker
testCost uint64
defParams flowcontrol.ServerParams
lesTopics []discv5.Topic lesTopics []discv5.Topic
privateKey *ecdsa.PrivateKey privateKey *ecdsa.PrivateKey
quitSync chan struct{}
onlyAnnounce bool
csvLogger *csvlogger.Logger
logTotalCap *csvlogger.Channel
thcNormal, thcBlockProcessing int // serving thread count for normal operation and block processing mode // Flow control and capacity management
fcManager *flowcontrol.ClientManager
maxPeers int costTracker *costTracker
minCapacity, freeClientCap uint64 defParams flowcontrol.ServerParams
servingQueue *servingQueue
freeClientPool *freeClientPool freeClientPool *freeClientPool
priorityClientPool *priorityClientPool 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) { func NewLesServer(e *eth.Ethereum, config *eth.Config) (*LesServer, error) {
var csvLogger *csvlogger.Logger // Collect les protocol version information supported by local node.
if logFileName != "" {
csvLogger = csvlogger.NewLogger(logFileName, time.Second*10, "event, peerId")
}
requestLogger := csvLogger
if !logRequestServing {
requestLogger = nil
}
lesTopics := make([]discv5.Topic, len(AdvertiseProtocolVersions)) lesTopics := make([]discv5.Topic, len(AdvertiseProtocolVersions))
for i, pv := range AdvertiseProtocolVersions { for i, pv := range AdvertiseProtocolVersions {
lesTopics[i] = lesTopic(e.BlockChain().Genesis().Hash(), pv) 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{ srv := &LesServer{
lesCommons: lesCommons{ lesCommons: lesCommons{
genesis: e.BlockChain().Genesis().Hash(),
config: config, config: config,
chainConfig: e.BlockChain().Config(),
iConfig: light.DefaultServerIndexerConfig, iConfig: light.DefaultServerIndexerConfig,
chainDb: e.ChainDb(), chainDb: e.ChainDb(),
peers: newPeerSet(),
chainReader: e.BlockChain(),
chtIndexer: light.NewChtIndexer(e.ChainDb(), nil, params.CHTFrequency, params.HelperTrieProcessConfirmations), chtIndexer: light.NewChtIndexer(e.ChainDb(), nil, params.CHTFrequency, params.HelperTrieProcessConfirmations),
bloomTrieIndexer: light.NewBloomTrieIndexer(e.ChainDb(), nil, params.BloomBitsBlocks, params.BloomTrieFrequency), bloomTrieIndexer: light.NewBloomTrieIndexer(e.ChainDb(), nil, params.BloomBitsBlocks, params.BloomTrieFrequency),
closeCh: make(chan struct{}),
}, },
quitSync: quitSync,
lesTopics: lesTopics, lesTopics: lesTopics,
onlyAnnounce: config.OnlyAnnounce, fcManager: flowcontrol.NewClientManager(nil, &mclock.System{}),
csvLogger: csvLogger, servingQueue: newServingQueue(int64(time.Millisecond*10), float64(config.LightServ)/100, reqLogger),
logTotalCap: requestLogger.NewChannel("totalCapacity", 0.01), 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() // Initialize server capacity management fields.
srv.thcNormal = config.LightServ * 4 / 100 srv.freeClientCap = srv.minCapacity
if srv.thcNormal < 4 { srv.defParams = flowcontrol.ServerParams{
srv.thcNormal = 4 BufLimit: srv.freeClientCap * bufLimitRatio,
MinRecharge: srv.freeClientCap,
} }
srv.thcBlockProcessing = config.LightServ/100 + 1 // LES flow control tries to more or less guarantee the possibility for the
srv.fcManager = flowcontrol.NewClientManager(nil, &mclock.System{}) // 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() checkpoint := srv.latestLocalCheckpoint()
if !checkpoint.Empty() { 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) "chtroot", checkpoint.CHTRoot, "bloomroot", checkpoint.BloomRoot)
} }
srv.chtIndexer.Start(e.BlockChain()) 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 return srv, nil
} }
@ -143,113 +163,31 @@ func (s *LesServer) APIs() []rpc.API {
{ {
Namespace: "les", Namespace: "les",
Version: "1.0", Version: "1.0",
Service: NewPrivateLightAPI(&s.lesCommons, s.protocolManager.reg), Service: NewPrivateLightAPI(&s.lesCommons),
Public: false, 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 { 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 // Start starts the LES server
func (s *LesServer) Start(srvr *p2p.Server) { func (s *LesServer) Start(srvr *p2p.Server) {
s.maxPeers = s.config.LightPeers s.privateKey = srvr.PrivateKey
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,
}
}
}
maxCapacity := s.freeClientCap * uint64(s.maxPeers) s.logger.Start()
if totalRecharge > maxCapacity { s.handler.start()
maxCapacity = totalRecharge
} s.wg.Add(1)
s.fcManager.SetCapacityLimits(s.freeClientCap, maxCapacity, s.freeClientCap*2) go s.capacityManagement()
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.protocolManager.peers.notify(s.priorityClientPool)
s.csvLogger.Start()
s.startEventLoop()
s.protocolManager.Start(s.config.LightPeers)
if srvr.DiscV5 != nil { if srvr.DiscV5 != nil {
for _, topic := range s.lesTopics { for _, topic := range s.lesTopics {
topic := topic topic := topic
@ -258,12 +196,33 @@ func (s *LesServer) Start(srvr *p2p.Server) {
logger.Info("Starting topic registration") logger.Info("Starting topic registration")
defer logger.Info("Terminated 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) { 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. // SetClient sets the rpc client and starts running checkpoint contract if it is not yet watched.
func (s *LesServer) SetContractBackend(backend bind.ContractBackend) { func (s *LesServer) SetContractBackend(backend bind.ContractBackend) {
if s.protocolManager.reg != nil { if s.registrar != nil {
s.protocolManager.reg.start(backend) s.registrar.start(backend)
} }
} }
// Stop stops the LES service // capacityManagement starts an event handler loop that updates the recharge curve of
func (s *LesServer) Stop() { // the client manager and adjusts the client pool's size according to the total
s.fcManager.Stop() // capacity updates coming from the client manager
s.chtIndexer.Close() func (s *LesServer) capacityManagement() {
// bloom trie indexer is closed by parent bloombits indexer defer s.wg.Done()
go func() {
<-s.protocolManager.noMorePeers
}()
s.freeClientPool.stop()
s.costTracker.stop()
s.protocolManager.Stop()
s.csvLogger.Stop()
}
// todo(rjl493456442) separate client and server implementation. processCh := make(chan bool, 100)
func (pm *ProtocolManager) blockLoop() { sub := s.handler.blockchain.SubscribeBlockProcessingEvent(processCh)
pm.wg.Add(1) defer sub.Unsubscribe()
headCh := make(chan core.ChainHeadEvent, 10)
headSub := pm.blockchain.SubscribeChainHeadEvent(headCh) totalRechargeCh := make(chan uint64, 100)
go func() { totalRecharge := s.costTracker.subscribeTotalRecharge(totalRechargeCh)
var lastHead *types.Header
lastBroadcastTd := common.Big0 totalCapacityCh := make(chan uint64, 100)
totalCapacity := s.fcManager.SubscribeTotalCapacity(totalCapacityCh)
s.priorityClientPool.setLimits(s.config.LightPeers, totalCapacity)
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 { for {
select { select {
case ev := <-headCh: case busy = <-processCh:
peers := pm.peers.AllPeers() updateRecharge()
if len(peers) > 0 { case totalRecharge = <-totalRechargeCh:
header := ev.Block.Header() updateRecharge()
hash := header.Hash() case totalCapacity = <-totalCapacityCh:
number := header.Number.Uint64() totalCapChannel.Update(float64(totalCapacity))
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
log.Debug("Announcing block to peers", "number", number, "hash", hash, "td", td, "reorg", reorg) newFreePeers := totalCapacity / s.freeClientCap
if newFreePeers < freePeers && newFreePeers < uint64(s.config.LightPeers) {
announce := announceData{Hash: hash, Number: number, Td: td, ReorgDepth: reorg} log.Warn("Reduced free peer connections", "from", freePeers, "to", newFreePeers)
var (
signed bool
signedAnnounce announceData
)
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) }) freePeers = newFreePeers
} s.priorityClientPool.setLimits(s.config.LightPeers, totalCapacity)
} case <-s.closeCh:
}
}
case <-pm.quitSync:
headSub.Unsubscribe()
pm.wg.Done()
return 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/common/mclock"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
@ -115,8 +116,6 @@ type serverPool struct {
db ethdb.Database db ethdb.Database
dbKey []byte dbKey []byte
server *p2p.Server server *p2p.Server
quit chan struct{}
wg *sync.WaitGroup
connWg sync.WaitGroup connWg sync.WaitGroup
topic discv5.Topic topic discv5.Topic
@ -137,14 +136,19 @@ type serverPool struct {
connCh chan *connReq connCh chan *connReq
disconnCh chan *disconnReq disconnCh chan *disconnReq
registerCh chan *registerReq registerCh chan *registerReq
closeCh chan struct{}
wg sync.WaitGroup
} }
// newServerPool creates a new serverPool instance // 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{ pool := &serverPool{
db: db, db: db,
quit: quit,
wg: wg,
entries: make(map[enode.ID]*poolEntry), entries: make(map[enode.ID]*poolEntry),
timeout: make(chan *poolEntry, 1), timeout: make(chan *poolEntry, 1),
adjustStats: make(chan poolStatAdjust, 100), 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), connCh: make(chan *connReq),
disconnCh: make(chan *disconnReq), disconnCh: make(chan *disconnReq),
registerCh: make(chan *registerReq), registerCh: make(chan *registerReq),
closeCh: make(chan struct{}),
knownSelect: newWeightedRandomSelect(), knownSelect: newWeightedRandomSelect(),
newSelect: newWeightedRandomSelect(), newSelect: newWeightedRandomSelect(),
fastDiscover: true, fastDiscover: true,
@ -167,7 +172,6 @@ func (pool *serverPool) start(server *p2p.Server, topic discv5.Topic) {
pool.server = server pool.server = server
pool.topic = topic pool.topic = topic
pool.dbKey = append([]byte("serverPool/"), []byte(topic)...) pool.dbKey = append([]byte("serverPool/"), []byte(topic)...)
pool.wg.Add(1)
pool.loadNodes() pool.loadNodes()
pool.connectToTrustedNodes() pool.connectToTrustedNodes()
@ -178,9 +182,15 @@ func (pool *serverPool) start(server *p2p.Server, topic discv5.Topic) {
go pool.discoverNodes() go pool.discoverNodes()
} }
pool.checkDial() pool.checkDial()
pool.wg.Add(1)
go pool.eventLoop() go pool.eventLoop()
} }
func (pool *serverPool) stop() {
close(pool.closeCh)
pool.wg.Wait()
}
// discoverNodes wraps SearchTopic, converting result nodes to enode.Node. // discoverNodes wraps SearchTopic, converting result nodes to enode.Node.
func (pool *serverPool) discoverNodes() { func (pool *serverPool) discoverNodes() {
ch := make(chan *discv5.Node) 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)} req := &connReq{p: p, node: node, result: make(chan *poolEntry, 1)}
select { select {
case pool.connCh <- req: case pool.connCh <- req:
case <-pool.quit: case <-pool.closeCh:
return nil return nil
} }
return <-req.result return <-req.result
@ -219,7 +229,7 @@ func (pool *serverPool) registered(entry *poolEntry) {
req := &registerReq{entry: entry, done: make(chan struct{})} req := &registerReq{entry: entry, done: make(chan struct{})}
select { select {
case pool.registerCh <- req: case pool.registerCh <- req:
case <-pool.quit: case <-pool.closeCh:
return return
} }
<-req.done <-req.done
@ -231,7 +241,7 @@ func (pool *serverPool) registered(entry *poolEntry) {
func (pool *serverPool) disconnect(entry *poolEntry) { func (pool *serverPool) disconnect(entry *poolEntry) {
stopped := false stopped := false
select { select {
case <-pool.quit: case <-pool.closeCh:
stopped = true stopped = true
default: 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 // eventLoop handles pool events and mutex locking for all internal functions
func (pool *serverPool) eventLoop() { func (pool *serverPool) eventLoop() {
defer pool.wg.Done()
lookupCnt := 0 lookupCnt := 0
var convTime mclock.AbsTime var convTime mclock.AbsTime
if pool.discSetPeriod != nil { if pool.discSetPeriod != nil {
@ -361,7 +372,7 @@ func (pool *serverPool) eventLoop() {
case req := <-pool.connCh: case req := <-pool.connCh:
if pool.trustedNodes[req.p.ID()] != nil { if pool.trustedNodes[req.p.ID()] != nil {
// ignore trusted nodes // ignore trusted nodes
req.result <- nil req.result <- &poolEntry{trusted: true}
} else { } else {
// Handle peer connection requests. // Handle peer connection requests.
entry := pool.entries[req.p.ID()] entry := pool.entries[req.p.ID()]
@ -389,6 +400,9 @@ func (pool *serverPool) eventLoop() {
} }
case req := <-pool.registerCh: case req := <-pool.registerCh:
if req.entry.trusted {
continue
}
// Handle peer registration requests. // Handle peer registration requests.
entry := req.entry entry := req.entry
entry.state = psRegistered entry.state = psRegistered
@ -402,10 +416,13 @@ func (pool *serverPool) eventLoop() {
close(req.done) close(req.done)
case req := <-pool.disconnCh: case req := <-pool.disconnCh:
if req.entry.trusted {
continue
}
// Handle peer disconnection requests. // Handle peer disconnection requests.
disconnect(req, req.stopped) disconnect(req, req.stopped)
case <-pool.quit: case <-pool.closeCh:
if pool.discSetPeriod != nil { if pool.discSetPeriod != nil {
close(pool.discSetPeriod) close(pool.discSetPeriod)
} }
@ -421,7 +438,6 @@ func (pool *serverPool) eventLoop() {
disconnect(req, true) disconnect(req, true)
} }
pool.saveNodes() pool.saveNodes()
pool.wg.Done()
return return
} }
} }
@ -549,10 +565,10 @@ func (pool *serverPool) setRetryDial(entry *poolEntry) {
entry.delayedRetry = true entry.delayedRetry = true
go func() { go func() {
select { select {
case <-pool.quit: case <-pool.closeCh:
case <-time.After(delay): case <-time.After(delay):
select { select {
case <-pool.quit: case <-pool.closeCh:
case pool.enableRetry <- entry: case pool.enableRetry <- entry:
} }
} }
@ -618,10 +634,10 @@ func (pool *serverPool) dial(entry *poolEntry, knownSelected bool) {
go func() { go func() {
pool.server.AddPeer(entry.node) pool.server.AddPeer(entry.node)
select { select {
case <-pool.quit: case <-pool.closeCh:
case <-time.After(dialTimeout): case <-time.After(dialTimeout):
select { select {
case <-pool.quit: case <-pool.closeCh:
case pool.timeout <- entry: case pool.timeout <- entry:
} }
} }
@ -663,7 +679,7 @@ type poolEntry struct {
addrSelect weightedRandomSelect addrSelect weightedRandomSelect
lastDiscovered mclock.AbsTime lastDiscovered mclock.AbsTime
known, knownSelected bool known, knownSelected, trusted bool
connectStats, delayStats poolStats connectStats, delayStats poolStats
responseStats, timeoutStats poolStats responseStats, timeoutStats poolStats
state int state int

View file

@ -44,35 +44,6 @@ const (
checkpointSync 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. // validateCheckpoint verifies the advertised checkpoint by peer is valid or not.
// //
// Each network has several hard-coded checkpoint signer addresses. Only the // 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 // In addition to the checkpoint registered in the registrar contract, there are
// several legacy hardcoded checkpoints in our codebase. These checkpoints are // several legacy hardcoded checkpoints in our codebase. These checkpoints are
// also considered as valid. // 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) ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel() defer cancel()
// Fetch the block header corresponding to the checkpoint registration. // Fetch the block header corresponding to the checkpoint registration.
cp := peer.advertisedCheckpoint 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 { if err != nil {
return err return err
} }
// Fetch block logs associated with the block header. // 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 { if err != nil {
return err 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 { if len(events) == 0 {
return errInvalidCheckpoint return errInvalidCheckpoint
} }
@ -108,7 +79,7 @@ func (pm *ProtocolManager) validateCheckpoint(peer *peer) error {
for _, event := range events { for _, event := range events {
signatures = append(signatures, append(event.R[:], append(event.S[:], event.V)...)) 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 { if !valid {
return errInvalidCheckpoint 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. // 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. // Short circuit if the peer is nil.
if peer == nil { if peer == nil {
return return
} }
// Make sure the peer's TD is higher than our own. // Make sure the peer's TD is higher than our own.
latest := pm.blockchain.CurrentHeader() latest := h.backend.blockchain.CurrentHeader()
currentTd := rawdb.ReadTd(pm.chainDb, latest.Hash(), latest.Number.Uint64()) currentTd := rawdb.ReadTd(h.backend.chainDb, latest.Hash(), latest.Number.Uint64())
if currentTd != nil && peer.headBlockInfo().Td.Cmp(currentTd) < 0 { if currentTd != nil && peer.headBlockInfo().Td.Cmp(currentTd) < 0 {
return return
} }
@ -142,34 +113,34 @@ func (pm *ProtocolManager) synchronise(peer *peer) {
case cp.Empty(): case cp.Empty():
mode = lightSync mode = lightSync
log.Debug("Disable checkpoint syncing", "reason", "empty checkpoint") 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 mode = lightSync
log.Debug("Disable checkpoint syncing", "reason", "local chain beyonds the checkpoint") log.Debug("Disable checkpoint syncing", "reason", "local chain beyonds the checkpoint")
case peer.isHardcode: case peer.isHardcode:
mode = legacyCheckpointSync mode = legacyCheckpointSync
log.Debug("Disable checkpoint syncing", "reason", "checkpoint is hardcoded") 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 mode = legacyCheckpointSync
log.Debug("Disable checkpoint syncing", "reason", "checkpoint syncing is not activated") log.Debug("Disable checkpoint syncing", "reason", "checkpoint syncing is not activated")
} }
// Notify testing framework if syncing has completed(for testing purpose). // Notify testing framework if syncing has completed(for testing purpose).
defer func() { defer func() {
if pm.reg != nil && pm.reg.syncDoneHook != nil { if h.backend.registrar != nil && h.backend.registrar.syncDoneHook != nil {
pm.reg.syncDoneHook() h.backend.registrar.syncDoneHook()
} }
}() }()
start := time.Now() start := time.Now()
if mode == checkpointSync || mode == legacyCheckpointSync { if mode == checkpointSync || mode == legacyCheckpointSync {
// Validate the advertised checkpoint // Validate the advertised checkpoint
if mode == legacyCheckpointSync { if mode == legacyCheckpointSync {
cp = params.TrustedCheckpoints[pm.blockchain.Genesis().Hash()] cp = params.TrustedCheckpoints[h.backend.blockchain.Genesis().Hash()]
} else if mode == checkpointSync { } 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) log.Debug("Failed to validate checkpoint", "reason", err)
pm.removePeer(peer.id) h.removePeer(peer.id)
return return
} }
pm.blockchain.(*light.LightChain).AddTrustedCheckpoint(cp) h.backend.blockchain.AddTrustedCheckpoint(cp)
} }
log.Debug("Checkpoint syncing start", "peer", peer.id, "checkpoint", cp.SectionIndex) 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. // of the latest epoch covered by checkpoint.
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel() 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") log.Debug("Sync checkpoint failed")
pm.removePeer(peer.id) h.removePeer(peer.id)
return return
} }
} }
// Fetch the remaining block headers based on the current chain header. // 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) log.Debug("Synchronise failed", "reason", err)
return return
} }

View file

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

View file

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

View file

@ -44,7 +44,7 @@ func newULC(ulcConfig *eth.ULCConfig) *ulc {
return &ulc{m, ulcConfig.MinTrustedFraction} 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 { func (u *ulc) isTrusted(p enode.ID) bool {
if u.trustedKeys == nil { if u.trustedKeys == nil {
return false return false

View file

@ -17,184 +17,105 @@
package les package les
import ( import (
"crypto/ecdsa" "crypto/rand"
"fmt" "fmt"
"math/big"
"net" "net"
"reflect"
"testing" "testing"
"time" "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/crypto"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enode"
) )
func TestULCSyncWithOnePeer(t *testing.T) { func TestULCAnnounceThresholdLes2(t *testing.T) { testULCAnnounceThreshold(t, 2) }
f := newFullPeerPair(t, 1, 4) func TestULCAnnounceThresholdLes3(t *testing.T) { testULCAnnounceThreshold(t, 3) }
ulcConfig := &eth.ULCConfig{
MinTrustedFraction: 100, func testULCAnnounceThreshold(t *testing.T, protocol int) {
TrustedServers: []string{f.Node.String()}, // 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)
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)
// 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)
} }
l := newLightPeer(t, ulcConfig) // Release all servers and client resources.
teardown()
if reflect.DeepEqual(f.PM.blockchain.CurrentHeader().Hash(), l.PM.blockchain.CurrentHeader().Hash()) { for i := 0; i < len(teardowns); i++ {
t.Fatal("blocks are equal") teardowns[i]()
} }
_, _, 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")
} }
} }
func TestULCReceiveAnnounce(t *testing.T) { func connect(server *serverHandler, serverId enode.ID, client *clientHandler, protocol int) (*peer, *peer, error) {
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) {
// Create a message pipe to communicate through // Create a message pipe to communicate through
app, net := p2p.MsgPipe() app, net := p2p.MsgPipe()
peerLight := full.PM.newPeer(version, NetworkId, p2p.NewPeer(light.Node.ID(), light.Name, nil), net) var id enode.ID
peerFull := light.PM.newPeer(version, NetworkId, p2p.NewPeer(full.Node.ID(), full.Name, nil), app) 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 // Start the peerLight on a new thread
errc1 := make(chan error, 1) errc1 := make(chan error, 1)
errc2 := make(chan error, 1) errc2 := make(chan error, 1)
go func() { go func() {
select { select {
case light.PM.newPeerCh <- peerFull: case <-server.closeCh:
errc1 <- light.PM.handle(peerFull)
case <-light.PM.quitSync:
errc1 <- p2p.DiscQuitting errc1 <- p2p.DiscQuitting
case errc1 <- server.handle(peer2):
} }
}() }()
go func() { go func() {
select { select {
case full.PM.newPeerCh <- peerLight: case <-client.closeCh:
errc2 <- full.PM.handle(peerLight) errc1 <- p2p.DiscQuitting
case <-full.PM.quitSync: case errc1 <- client.handle(peer1):
errc2 <- p2p.DiscQuitting
} }
}() }()
@ -205,49 +126,23 @@ func connectPeers(full, light pairPeer, version int) (*peer, *peer, error) {
case err := <-errc2: case err := <-errc2:
return nil, nil, fmt.Errorf("peerFull handshake error: %v", err) return nil, nil, fmt.Errorf("peerFull handshake error: %v", err)
} }
return peer1, peer2, nil
return peerFull, peerLight, nil
} }
// newFullPeerPair creates node with full sync mode // newServerPeer creates server peer.
func newFullPeerPair(t *testing.T, index int, numberOfblocks int) pairPeer { func newServerPeer(t *testing.T, blocks int, protocol int) (*testServer, *enode.Node, func()) {
db := rawdb.NewMemoryDatabase() s, teardown := newServerEnv(t, blocks, protocol, nil, false, false, 0)
pmFull, _ := newTestProtocolManagerMust(t, false, numberOfblocks, nil, nil, nil, db, nil)
peerPairFull := pairPeer{
Name: "full node",
PM: pmFull,
}
key, err := crypto.GenerateKey() key, err := crypto.GenerateKey()
if err != nil { if err != nil {
t.Fatal("generate key err:", err) t.Fatal("generate key err:", err)
} }
peerPairFull.Key = key s.handler.server.privateKey = key
peerPairFull.Node = enode.NewV4(&key.PublicKey, net.ParseIP("127.0.0.1"), 35000, 35000) n := enode.NewV4(&key.PublicKey, net.ParseIP("127.0.0.1"), 35000, 35000)
return peerPairFull return s, n, teardown
} }
// newLightPeer creates node with light sync mode // newLightPeer creates node with light sync mode
func newLightPeer(t *testing.T, ulcConfig *eth.ULCConfig) pairPeer { func newLightPeer(t *testing.T, protocol int, ulcConfig *eth.ULCConfig) (*testClient, func()) {
peers := newPeerSet() _, c, teardown := newClientServerEnv(t, 0, protocol, nil, ulcConfig, false, false)
dist := newRequestDistributor(peers, make(chan struct{}), &mclock.System{}) return c, teardown
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
} }