2015-04-12 13:38:25 +03:00
|
|
|
package downloader
|
|
|
|
|
|
|
|
import (
|
2015-04-12 14:33:42 +03:00
|
|
|
"errors"
|
2015-04-12 13:38:25 +03:00
|
|
|
"math/big"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
2015-04-18 19:54:57 +03:00
|
|
|
"gopkg.in/fatih/set.v0"
|
2015-04-12 13:38:25 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
workingState = 2
|
|
|
|
idleState = 4
|
|
|
|
)
|
|
|
|
|
2015-04-13 17:38:32 +03:00
|
|
|
type hashFetcherFn func(common.Hash) error
|
|
|
|
type blockFetcherFn func([]common.Hash) error
|
|
|
|
|
|
|
|
// XXX make threadsafe!!!!
|
|
|
|
type peers map[string]*peer
|
|
|
|
|
2015-04-18 21:25:55 +03:00
|
|
|
func (p peers) reset() {
|
|
|
|
for _, peer := range p {
|
2015-04-18 21:35:49 +03:00
|
|
|
peer.reset()
|
2015-04-18 21:25:55 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-13 17:38:32 +03:00
|
|
|
func (p peers) get(state int) []*peer {
|
|
|
|
var peers []*peer
|
|
|
|
for _, peer := range p {
|
|
|
|
peer.mu.RLock()
|
|
|
|
if peer.state == state {
|
|
|
|
peers = append(peers, peer)
|
|
|
|
}
|
|
|
|
peer.mu.RUnlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
return peers
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p peers) setState(id string, state int) {
|
|
|
|
if peer, exist := p[id]; exist {
|
|
|
|
peer.mu.Lock()
|
|
|
|
defer peer.mu.Unlock()
|
|
|
|
peer.state = state
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p peers) getPeer(id string) *peer {
|
|
|
|
return p[id]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p peers) bestPeer() *peer {
|
|
|
|
var peer *peer
|
|
|
|
for _, cp := range p {
|
|
|
|
if peer == nil || cp.td.Cmp(peer.td) > 0 {
|
|
|
|
peer = cp
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return peer
|
|
|
|
}
|
|
|
|
|
2015-04-12 13:38:25 +03:00
|
|
|
// peer represents an active peer
|
|
|
|
type peer struct {
|
2015-04-13 17:38:32 +03:00
|
|
|
state int // Peer state (working, idle)
|
|
|
|
rep int // TODO peer reputation
|
2015-04-12 13:38:25 +03:00
|
|
|
|
|
|
|
mu sync.RWMutex
|
|
|
|
id string
|
|
|
|
td *big.Int
|
|
|
|
recentHash common.Hash
|
|
|
|
|
2015-04-18 19:54:57 +03:00
|
|
|
requested *set.Set
|
|
|
|
|
2015-04-12 13:38:25 +03:00
|
|
|
getHashes hashFetcherFn
|
|
|
|
getBlocks blockFetcherFn
|
|
|
|
}
|
|
|
|
|
|
|
|
// create a new peer
|
|
|
|
func newPeer(id string, td *big.Int, hash common.Hash, getHashes hashFetcherFn, getBlocks blockFetcherFn) *peer {
|
2015-04-18 19:54:57 +03:00
|
|
|
return &peer{
|
|
|
|
id: id,
|
|
|
|
td: td,
|
|
|
|
recentHash: hash,
|
|
|
|
getHashes: getHashes,
|
|
|
|
getBlocks: getBlocks,
|
|
|
|
state: idleState,
|
|
|
|
requested: set.New(),
|
|
|
|
}
|
2015-04-12 13:38:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// fetch a chunk using the peer
|
2015-04-12 14:33:42 +03:00
|
|
|
func (p *peer) fetch(chunk *chunk) error {
|
2015-04-12 13:38:25 +03:00
|
|
|
p.mu.Lock()
|
|
|
|
defer p.mu.Unlock()
|
|
|
|
|
2015-04-12 14:33:42 +03:00
|
|
|
if p.state == workingState {
|
|
|
|
return errors.New("peer already fetching chunk")
|
|
|
|
}
|
|
|
|
|
2015-04-18 19:54:57 +03:00
|
|
|
p.requested.Merge(chunk.hashes)
|
|
|
|
|
2015-04-12 13:38:25 +03:00
|
|
|
// set working state
|
|
|
|
p.state = workingState
|
|
|
|
// convert the set to a fetchable slice
|
|
|
|
hashes, i := make([]common.Hash, chunk.hashes.Size()), 0
|
|
|
|
chunk.hashes.Each(func(v interface{}) bool {
|
|
|
|
hashes[i] = v.(common.Hash)
|
|
|
|
i++
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
p.getBlocks(hashes)
|
2015-04-12 14:33:42 +03:00
|
|
|
|
|
|
|
return nil
|
2015-04-12 13:38:25 +03:00
|
|
|
}
|
2015-04-16 03:16:33 +03:00
|
|
|
|
|
|
|
// promote increases the peer's reputation
|
|
|
|
func (p *peer) promote() {
|
|
|
|
p.mu.Lock()
|
|
|
|
defer p.mu.Unlock()
|
|
|
|
|
|
|
|
p.rep++
|
|
|
|
}
|
|
|
|
|
|
|
|
// demote decreases the peer's reputation or leaves it at 0
|
|
|
|
func (p *peer) demote() {
|
|
|
|
p.mu.Lock()
|
|
|
|
defer p.mu.Unlock()
|
|
|
|
|
|
|
|
if p.rep > 1 {
|
|
|
|
p.rep -= 2
|
|
|
|
} else {
|
|
|
|
p.rep = 0
|
|
|
|
}
|
|
|
|
}
|
2015-04-18 21:25:55 +03:00
|
|
|
|
|
|
|
func (p *peer) reset() {
|
|
|
|
p.state = idleState
|
|
|
|
}
|