diff options
author | Zsolt Felfoldi <zsfelfoldi@gmail.com> | 2016-10-14 11:51:29 +0800 |
---|---|---|
committer | Felix Lange <fjl@twurst.com> | 2016-11-09 09:12:53 +0800 |
commit | 9f8d192991c4f68fa14c91366722bbca601da117 (patch) | |
tree | 5c1e089673d3f0208cd4a8208623bb95f29622c9 /les/fetcher.go | |
parent | 760fd65487614b7a61443cd9371015925795f40f (diff) | |
download | dexon-9f8d192991c4f68fa14c91366722bbca601da117.tar dexon-9f8d192991c4f68fa14c91366722bbca601da117.tar.gz dexon-9f8d192991c4f68fa14c91366722bbca601da117.tar.bz2 dexon-9f8d192991c4f68fa14c91366722bbca601da117.tar.lz dexon-9f8d192991c4f68fa14c91366722bbca601da117.tar.xz dexon-9f8d192991c4f68fa14c91366722bbca601da117.tar.zst dexon-9f8d192991c4f68fa14c91366722bbca601da117.zip |
les: light client protocol and API
Diffstat (limited to 'les/fetcher.go')
-rw-r--r-- | les/fetcher.go | 295 |
1 files changed, 295 insertions, 0 deletions
diff --git a/les/fetcher.go b/les/fetcher.go new file mode 100644 index 000000000..3fa5cf0e2 --- /dev/null +++ b/les/fetcher.go @@ -0,0 +1,295 @@ +// Copyright 2015 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 implements the Light Ethereum Subprotocol. +package les + +import ( + "math/big" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" +) + +type lightFetcher struct { + pm *ProtocolManager + odr *LesOdr + chain BlockChain + + headAnnouncedMu sync.Mutex + headAnnouncedBy map[common.Hash][]*peer + currentTd *big.Int + deliverChn chan fetchResponse + reqMu sync.RWMutex + requested map[uint64]fetchRequest + timeoutChn chan uint64 + notifyChn chan bool // true if initiated from outside + syncing bool + syncDone chan struct{} +} + +type fetchRequest struct { + hash common.Hash + amount uint64 + peer *peer +} + +type fetchResponse struct { + reqID uint64 + headers []*types.Header +} + +func newLightFetcher(pm *ProtocolManager) *lightFetcher { + f := &lightFetcher{ + pm: pm, + chain: pm.blockchain, + odr: pm.odr, + headAnnouncedBy: make(map[common.Hash][]*peer), + deliverChn: make(chan fetchResponse, 100), + requested: make(map[uint64]fetchRequest), + timeoutChn: make(chan uint64), + notifyChn: make(chan bool, 100), + syncDone: make(chan struct{}), + currentTd: big.NewInt(0), + } + go f.syncLoop() + return f +} + +func (f *lightFetcher) notify(p *peer, head *announceData) { + var headHash common.Hash + if head == nil { + // initial notify + headHash = p.Head() + } else { + if core.GetTd(f.pm.chainDb, head.Hash, head.Number) != nil { + head.haveHeaders = head.Number + } + //fmt.Println("notify", p.id, head.Number, head.ReorgDepth, head.haveHeaders) + if !p.addNotify(head) { + //fmt.Println("addNotify fail") + f.pm.removePeer(p.id) + } + headHash = head.Hash + } + f.headAnnouncedMu.Lock() + f.headAnnouncedBy[headHash] = append(f.headAnnouncedBy[headHash], p) + f.headAnnouncedMu.Unlock() + f.notifyChn <- true +} + +func (f *lightFetcher) gotHeader(header *types.Header) { + f.headAnnouncedMu.Lock() + defer f.headAnnouncedMu.Unlock() + + hash := header.Hash() + peerList := f.headAnnouncedBy[hash] + if peerList == nil { + return + } + number := header.GetNumberU64() + td := core.GetTd(f.pm.chainDb, hash, number) + for _, peer := range peerList { + peer.lock.Lock() + ok := peer.gotHeader(hash, number, td) + peer.lock.Unlock() + if !ok { + //fmt.Println("gotHeader fail") + f.pm.removePeer(peer.id) + } + } + delete(f.headAnnouncedBy, hash) +} + +func (f *lightFetcher) nextRequest() (*peer, *announceData) { + var bestPeer *peer + bestTd := f.currentTd + for _, peer := range f.pm.peers.AllPeers() { + peer.lock.RLock() + if !peer.headInfo.requested && (peer.headInfo.Td.Cmp(bestTd) > 0 || + (bestPeer != nil && peer.headInfo.Td.Cmp(bestTd) == 0 && peer.headInfo.haveHeaders > bestPeer.headInfo.haveHeaders)) { + bestPeer = peer + bestTd = peer.headInfo.Td + } + peer.lock.RUnlock() + } + if bestPeer == nil { + return nil, nil + } + bestPeer.lock.Lock() + res := bestPeer.headInfo + res.requested = true + bestPeer.lock.Unlock() + for _, peer := range f.pm.peers.AllPeers() { + if peer != bestPeer { + peer.lock.Lock() + if peer.headInfo.Hash == bestPeer.headInfo.Hash && peer.headInfo.haveHeaders == bestPeer.headInfo.haveHeaders { + peer.headInfo.requested = true + } + peer.lock.Unlock() + } + } + return bestPeer, res +} + +func (f *lightFetcher) deliverHeaders(reqID uint64, headers []*types.Header) { + f.deliverChn <- fetchResponse{reqID: reqID, headers: headers} +} + +func (f *lightFetcher) requestedID(reqID uint64) bool { + f.reqMu.RLock() + _, ok := f.requested[reqID] + f.reqMu.RUnlock() + return ok +} + +func (f *lightFetcher) request(p *peer, block *announceData) { + //fmt.Println("request", p.id, block.Number, block.haveHeaders) + amount := block.Number - block.haveHeaders + if amount == 0 { + return + } + if amount > 100 { + f.syncing = true + go func() { + //fmt.Println("f.pm.synchronise(p)") + f.pm.synchronise(p) + //fmt.Println("sync done") + f.syncDone <- struct{}{} + }() + return + } + + reqID := f.odr.getNextReqID() + f.reqMu.Lock() + f.requested[reqID] = fetchRequest{hash: block.Hash, amount: amount, peer: p} + f.reqMu.Unlock() + cost := p.GetRequestCost(GetBlockHeadersMsg, int(amount)) + p.fcServer.SendRequest(reqID, cost) + go p.RequestHeadersByHash(reqID, cost, block.Hash, int(amount), 0, true) + go func() { + time.Sleep(hardRequestTimeout) + f.timeoutChn <- reqID + }() +} + +func (f *lightFetcher) processResponse(req fetchRequest, resp fetchResponse) bool { + if uint64(len(resp.headers)) != req.amount || resp.headers[0].Hash() != req.hash { + return false + } + headers := make([]*types.Header, req.amount) + for i, header := range resp.headers { + headers[int(req.amount)-1-i] = header + } + if _, err := f.chain.InsertHeaderChain(headers, 1); err != nil { + return false + } + for _, header := range headers { + td := core.GetTd(f.pm.chainDb, header.Hash(), header.GetNumberU64()) + if td == nil { + return false + } + if td.Cmp(f.currentTd) > 0 { + f.currentTd = td + } + f.gotHeader(header) + } + return true +} + +func (f *lightFetcher) checkSyncedHeaders() { + //fmt.Println("checkSyncedHeaders()") + for _, peer := range f.pm.peers.AllPeers() { + peer.lock.Lock() + h := peer.firstHeadInfo + remove := false + loop: + for h != nil { + if td := core.GetTd(f.pm.chainDb, h.Hash, h.Number); td != nil { + //fmt.Println(" found", h.Number) + ok := peer.gotHeader(h.Hash, h.Number, td) + if !ok { + remove = true + break loop + } + if td.Cmp(f.currentTd) > 0 { + f.currentTd = td + } + } + h = h.next + } + peer.lock.Unlock() + if remove { + //fmt.Println("checkSync fail") + f.pm.removePeer(peer.id) + } + } +} + +func (f *lightFetcher) syncLoop() { + f.pm.wg.Add(1) + defer f.pm.wg.Done() + + srtoNotify := false + for { + select { + case <-f.pm.quitSync: + return + case ext := <-f.notifyChn: + //fmt.Println("<-f.notifyChn", f.syncing, ext, srtoNotify) + s := srtoNotify + srtoNotify = false + if !f.syncing && !(ext && s) { + if p, r := f.nextRequest(); r != nil { + srtoNotify = true + go func() { + time.Sleep(softRequestTimeout) + f.notifyChn <- false + }() + f.request(p, r) + } + } + case reqID := <-f.timeoutChn: + f.reqMu.Lock() + req, ok := f.requested[reqID] + if ok { + delete(f.requested, reqID) + } + f.reqMu.Unlock() + if ok { + //fmt.Println("hard timeout") + f.pm.removePeer(req.peer.id) + } + case resp := <-f.deliverChn: + //fmt.Println("<-f.deliverChn", f.syncing) + f.reqMu.Lock() + req, ok := f.requested[resp.reqID] + delete(f.requested, resp.reqID) + f.reqMu.Unlock() + if !ok || !(f.syncing || f.processResponse(req, resp)) { + //fmt.Println("processResponse fail") + f.pm.removePeer(req.peer.id) + } + case <-f.syncDone: + //fmt.Println("<-f.syncDone", f.syncing) + f.checkSyncedHeaders() + f.syncing = false + } + } +} |