package downloader import ( "math" "sync" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "gopkg.in/fatih/set.v0" ) // queue represents hashes that are either need fetching or are being fetched type queue struct { hashPool *set.Set fetchPool *set.Set blockHashes *set.Set mu sync.Mutex fetching map[string]*chunk blockOffset int blocks []*types.Block } func newqueue() *queue { return &queue{ hashPool: set.New(), fetchPool: set.New(), blockHashes: set.New(), fetching: make(map[string]*chunk), } } func (c *queue) reset() { c.mu.Lock() defer c.mu.Unlock() c.resetNoTS() } func (c *queue) resetNoTS() { c.blockOffset = 0 c.hashPool.Clear() c.fetchPool.Clear() c.blockHashes.Clear() c.blocks = nil c.fetching = make(map[string]*chunk) } func (c *queue) size() int { return c.hashPool.Size() + c.blockHashes.Size() + c.fetchPool.Size() } // reserve a `max` set of hashes for `p` peer. func (c *queue) get(p *peer, max int) *chunk { c.mu.Lock() defer c.mu.Unlock() // return nothing if the pool has been depleted if c.hashPool.Size() == 0 { return nil } limit := int(math.Min(float64(max), float64(c.hashPool.Size()))) // Create a new set of hashes hashes, i := set.New(), 0 c.hashPool.Each(func(v interface{}) bool { // break on limit if i == limit { return false } // skip any hashes that have previously been requested from the peer if p.ignored.Has(v) { return true } hashes.Add(v) i++ return true }) // if no hashes can be requested return a nil chunk if hashes.Size() == 0 { return nil } // remove the fetchable hashes from hash pool c.hashPool.Separate(hashes) c.fetchPool.Merge(hashes) // Create a new chunk for the seperated hashes. The time is being used // to reset the chunk (timeout) chunk := &chunk{p, hashes, time.Now()} // register as 'fetching' state c.fetching[p.id] = chunk // create new chunk for peer return chunk } func (c *queue) has(hash common.Hash) bool { return c.hashPool.Has(hash) || c.fetchPool.Has(hash) || c.blockHashes.Has(hash) } func (c *queue) addBlock(id string, block *types.Block) { c.mu.Lock() defer c.mu.Unlock() // when adding a block make sure it doesn't already exist if !c.blockHashes.Has(block.Hash()) { c.hashPool.Remove(block.Hash()) c.blocks = append(c.blocks, block) } } func (c *queue) getBlock(hash common.Hash) *types.Block { c.mu.Lock() defer c.mu.Unlock() if !c.blockHashes.Has(hash) { return nil } for _, block := range c.blocks { if block.Hash() == hash { return block } } return nil } // deliver delivers a chunk to the queue that was requested of the peer func (c *queue) deliver(id string, blocks []*types.Block) error { c.mu.Lock() defer c.mu.Unlock() chunk := c.fetching[id] // If the chunk was never requested simply ignore it if chunk != nil { delete(c.fetching, id) // check the length of the returned blocks. If the length of blocks is 0 // we'll assume the peer doesn't know about the chain. if len(blocks) == 0 { // So we can ignore the blocks we didn't know about chunk.peer.ignored.Merge(chunk.hashes) } // seperate the blocks and the hashes blockHashes := chunk.fetchedHashes(blocks) // merge block hashes c.blockHashes.Merge(blockHashes) // Add the blocks for _, block := range blocks { // See (1) for future limitation n := int(block.NumberU64()) - c.blockOffset if n > len(c.blocks) || n < 0 { return errBlockNumberOverflow } c.blocks[n] = block } // Add back whatever couldn't be delivered c.hashPool.Merge(chunk.hashes) c.fetchPool.Separate(chunk.hashes) } return nil } func (c *queue) alloc(offset, size int) { c.mu.Lock() defer c.mu.Unlock() if c.blockOffset < offset { c.blockOffset = offset } // (1) XXX at some point we could limit allocation to memory and use the disk // to store future blocks. if len(c.blocks) < size { c.blocks = append(c.blocks, make([]*types.Block, size)...) } } // puts puts sets of hashes on to the queue for fetching func (c *queue) put(hashes *set.Set) { c.mu.Lock() defer c.mu.Unlock() c.hashPool.Merge(hashes) } type chunk struct { peer *peer hashes *set.Set itime time.Time } func (ch *chunk) fetchedHashes(blocks []*types.Block) *set.Set { fhashes := set.New() for _, block := range blocks { fhashes.Add(block.Hash()) } ch.hashes.Separate(fhashes) return fhashes }