github.com/jeffallen/go-ethereum@v1.1.4-0.20150910155051-571d3236c49c/eth/fetcher/fetcher.go (about)

     1  // Copyright 2015 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // Package fetcher contains the block announcement based synchonisation.
    18  package fetcher
    19  
    20  import (
    21  	"errors"
    22  	"fmt"
    23  	"math/rand"
    24  	"time"
    25  
    26  	"github.com/ethereum/go-ethereum/common"
    27  	"github.com/ethereum/go-ethereum/core"
    28  	"github.com/ethereum/go-ethereum/core/types"
    29  	"github.com/ethereum/go-ethereum/logger"
    30  	"github.com/ethereum/go-ethereum/logger/glog"
    31  	"gopkg.in/karalabe/cookiejar.v2/collections/prque"
    32  )
    33  
    34  const (
    35  	arriveTimeout = 500 * time.Millisecond // Time allowance before an announced block is explicitly requested
    36  	gatherSlack   = 100 * time.Millisecond // Interval used to collate almost-expired announces with fetches
    37  	fetchTimeout  = 5 * time.Second        // Maximum alloted time to return an explicitly requested block
    38  	maxUncleDist  = 7                      // Maximum allowed backward distance from the chain head
    39  	maxQueueDist  = 32                     // Maximum allowed distance from the chain head to queue
    40  	hashLimit     = 256                    // Maximum number of unique blocks a peer may have announced
    41  	blockLimit    = 64                     // Maximum number of unique blocks a per may have delivered
    42  )
    43  
    44  var (
    45  	errTerminated = errors.New("terminated")
    46  )
    47  
    48  // blockRetrievalFn is a callback type for retrieving a block from the local chain.
    49  type blockRetrievalFn func(common.Hash) *types.Block
    50  
    51  // blockRequesterFn is a callback type for sending a block retrieval request.
    52  type blockRequesterFn func([]common.Hash) error
    53  
    54  // blockValidatorFn is a callback type to verify a block's header for fast propagation.
    55  type blockValidatorFn func(block *types.Block, parent *types.Block) error
    56  
    57  // blockBroadcasterFn is a callback type for broadcasting a block to connected peers.
    58  type blockBroadcasterFn func(block *types.Block, propagate bool)
    59  
    60  // chainHeightFn is a callback type to retrieve the current chain height.
    61  type chainHeightFn func() uint64
    62  
    63  // chainInsertFn is a callback type to insert a batch of blocks into the local chain.
    64  type chainInsertFn func(types.Blocks) (int, error)
    65  
    66  // peerDropFn is a callback type for dropping a peer detected as malicious.
    67  type peerDropFn func(id string)
    68  
    69  // announce is the hash notification of the availability of a new block in the
    70  // network.
    71  type announce struct {
    72  	hash common.Hash // Hash of the block being announced
    73  	time time.Time   // Timestamp of the announcement
    74  
    75  	origin string           // Identifier of the peer originating the notification
    76  	fetch  blockRequesterFn // Fetcher function to retrieve
    77  }
    78  
    79  // inject represents a schedules import operation.
    80  type inject struct {
    81  	origin string
    82  	block  *types.Block
    83  }
    84  
    85  // Fetcher is responsible for accumulating block announcements from various peers
    86  // and scheduling them for retrieval.
    87  type Fetcher struct {
    88  	// Various event channels
    89  	notify chan *announce
    90  	inject chan *inject
    91  	filter chan chan []*types.Block
    92  	done   chan common.Hash
    93  	quit   chan struct{}
    94  
    95  	// Announce states
    96  	announces map[string]int              // Per peer announce counts to prevent memory exhaustion
    97  	announced map[common.Hash][]*announce // Announced blocks, scheduled for fetching
    98  	fetching  map[common.Hash]*announce   // Announced blocks, currently fetching
    99  
   100  	// Block cache
   101  	queue  *prque.Prque            // Queue containing the import operations (block number sorted)
   102  	queues map[string]int          // Per peer block counts to prevent memory exhaustion
   103  	queued map[common.Hash]*inject // Set of already queued blocks (to dedup imports)
   104  
   105  	// Callbacks
   106  	getBlock       blockRetrievalFn   // Retrieves a block from the local chain
   107  	validateBlock  blockValidatorFn   // Checks if a block's headers have a valid proof of work
   108  	broadcastBlock blockBroadcasterFn // Broadcasts a block to connected peers
   109  	chainHeight    chainHeightFn      // Retrieves the current chain's height
   110  	insertChain    chainInsertFn      // Injects a batch of blocks into the chain
   111  	dropPeer       peerDropFn         // Drops a peer for misbehaving
   112  
   113  	// Testing hooks
   114  	fetchingHook func([]common.Hash) // Method to call upon starting a block fetch
   115  	importedHook func(*types.Block)  // Method to call upon successful block import
   116  }
   117  
   118  // New creates a block fetcher to retrieve blocks based on hash announcements.
   119  func New(getBlock blockRetrievalFn, validateBlock blockValidatorFn, broadcastBlock blockBroadcasterFn, chainHeight chainHeightFn, insertChain chainInsertFn, dropPeer peerDropFn) *Fetcher {
   120  	return &Fetcher{
   121  		notify:         make(chan *announce),
   122  		inject:         make(chan *inject),
   123  		filter:         make(chan chan []*types.Block),
   124  		done:           make(chan common.Hash),
   125  		quit:           make(chan struct{}),
   126  		announces:      make(map[string]int),
   127  		announced:      make(map[common.Hash][]*announce),
   128  		fetching:       make(map[common.Hash]*announce),
   129  		queue:          prque.New(),
   130  		queues:         make(map[string]int),
   131  		queued:         make(map[common.Hash]*inject),
   132  		getBlock:       getBlock,
   133  		validateBlock:  validateBlock,
   134  		broadcastBlock: broadcastBlock,
   135  		chainHeight:    chainHeight,
   136  		insertChain:    insertChain,
   137  		dropPeer:       dropPeer,
   138  	}
   139  }
   140  
   141  // Start boots up the announcement based synchoniser, accepting and processing
   142  // hash notifications and block fetches until termination requested.
   143  func (f *Fetcher) Start() {
   144  	go f.loop()
   145  }
   146  
   147  // Stop terminates the announcement based synchroniser, canceling all pending
   148  // operations.
   149  func (f *Fetcher) Stop() {
   150  	close(f.quit)
   151  }
   152  
   153  // Notify announces the fetcher of the potential availability of a new block in
   154  // the network.
   155  func (f *Fetcher) Notify(peer string, hash common.Hash, time time.Time, fetcher blockRequesterFn) error {
   156  	block := &announce{
   157  		hash:   hash,
   158  		time:   time,
   159  		origin: peer,
   160  		fetch:  fetcher,
   161  	}
   162  	select {
   163  	case f.notify <- block:
   164  		return nil
   165  	case <-f.quit:
   166  		return errTerminated
   167  	}
   168  }
   169  
   170  // Enqueue tries to fill gaps the the fetcher's future import queue.
   171  func (f *Fetcher) Enqueue(peer string, block *types.Block) error {
   172  	op := &inject{
   173  		origin: peer,
   174  		block:  block,
   175  	}
   176  	select {
   177  	case f.inject <- op:
   178  		return nil
   179  	case <-f.quit:
   180  		return errTerminated
   181  	}
   182  }
   183  
   184  // Filter extracts all the blocks that were explicitly requested by the fetcher,
   185  // returning those that should be handled differently.
   186  func (f *Fetcher) Filter(blocks types.Blocks) types.Blocks {
   187  	// Send the filter channel to the fetcher
   188  	filter := make(chan []*types.Block)
   189  
   190  	select {
   191  	case f.filter <- filter:
   192  	case <-f.quit:
   193  		return nil
   194  	}
   195  	// Request the filtering of the block list
   196  	select {
   197  	case filter <- blocks:
   198  	case <-f.quit:
   199  		return nil
   200  	}
   201  	// Retrieve the blocks remaining after filtering
   202  	select {
   203  	case blocks := <-filter:
   204  		return blocks
   205  	case <-f.quit:
   206  		return nil
   207  	}
   208  }
   209  
   210  // Loop is the main fetcher loop, checking and processing various notification
   211  // events.
   212  func (f *Fetcher) loop() {
   213  	// Iterate the block fetching until a quit is requested
   214  	fetch := time.NewTimer(0)
   215  	for {
   216  		// Clean up any expired block fetches
   217  		for hash, announce := range f.fetching {
   218  			if time.Since(announce.time) > fetchTimeout {
   219  				f.forgetHash(hash)
   220  			}
   221  		}
   222  		// Import any queued blocks that could potentially fit
   223  		height := f.chainHeight()
   224  		for !f.queue.Empty() {
   225  			op := f.queue.PopItem().(*inject)
   226  
   227  			// If too high up the chain or phase, continue later
   228  			number := op.block.NumberU64()
   229  			if number > height+1 {
   230  				f.queue.Push(op, -float32(op.block.NumberU64()))
   231  				break
   232  			}
   233  			// Otherwise if fresh and still unknown, try and import
   234  			hash := op.block.Hash()
   235  			if number+maxUncleDist < height || f.getBlock(hash) != nil {
   236  				f.forgetBlock(hash)
   237  				continue
   238  			}
   239  			f.insert(op.origin, op.block)
   240  		}
   241  		// Wait for an outside event to occur
   242  		select {
   243  		case <-f.quit:
   244  			// Fetcher terminating, abort all operations
   245  			return
   246  
   247  		case notification := <-f.notify:
   248  			// A block was announced, make sure the peer isn't DOSing us
   249  			announceMeter.Mark(1)
   250  
   251  			count := f.announces[notification.origin] + 1
   252  			if count > hashLimit {
   253  				glog.V(logger.Debug).Infof("Peer %s: exceeded outstanding announces (%d)", notification.origin, hashLimit)
   254  				break
   255  			}
   256  			// All is well, schedule the announce if block's not yet downloading
   257  			if _, ok := f.fetching[notification.hash]; ok {
   258  				break
   259  			}
   260  			f.announces[notification.origin] = count
   261  			f.announced[notification.hash] = append(f.announced[notification.hash], notification)
   262  			if len(f.announced) == 1 {
   263  				f.reschedule(fetch)
   264  			}
   265  
   266  		case op := <-f.inject:
   267  			// A direct block insertion was requested, try and fill any pending gaps
   268  			broadcastMeter.Mark(1)
   269  			f.enqueue(op.origin, op.block)
   270  
   271  		case hash := <-f.done:
   272  			// A pending import finished, remove all traces of the notification
   273  			f.forgetHash(hash)
   274  			f.forgetBlock(hash)
   275  
   276  		case <-fetch.C:
   277  			// At least one block's timer ran out, check for needing retrieval
   278  			request := make(map[string][]common.Hash)
   279  
   280  			for hash, announces := range f.announced {
   281  				if time.Since(announces[0].time) > arriveTimeout-gatherSlack {
   282  					// Pick a random peer to retrieve from, reset all others
   283  					announce := announces[rand.Intn(len(announces))]
   284  					f.forgetHash(hash)
   285  
   286  					// If the block still didn't arrive, queue for fetching
   287  					if f.getBlock(hash) == nil {
   288  						request[announce.origin] = append(request[announce.origin], hash)
   289  						f.fetching[hash] = announce
   290  					}
   291  				}
   292  			}
   293  			// Send out all block requests
   294  			for peer, hashes := range request {
   295  				if glog.V(logger.Detail) && len(hashes) > 0 {
   296  					list := "["
   297  					for _, hash := range hashes {
   298  						list += fmt.Sprintf("%x, ", hash[:4])
   299  					}
   300  					list = list[:len(list)-2] + "]"
   301  
   302  					glog.V(logger.Detail).Infof("Peer %s: fetching %s", peer, list)
   303  				}
   304  				// Create a closure of the fetch and schedule in on a new thread
   305  				fetcher, hashes := f.fetching[hashes[0]].fetch, hashes
   306  				go func() {
   307  					if f.fetchingHook != nil {
   308  						f.fetchingHook(hashes)
   309  					}
   310  					fetcher(hashes)
   311  				}()
   312  			}
   313  			// Schedule the next fetch if blocks are still pending
   314  			f.reschedule(fetch)
   315  
   316  		case filter := <-f.filter:
   317  			// Blocks arrived, extract any explicit fetches, return all else
   318  			var blocks types.Blocks
   319  			select {
   320  			case blocks = <-filter:
   321  			case <-f.quit:
   322  				return
   323  			}
   324  
   325  			explicit, download := []*types.Block{}, []*types.Block{}
   326  			for _, block := range blocks {
   327  				hash := block.Hash()
   328  
   329  				// Filter explicitly requested blocks from hash announcements
   330  				if f.fetching[hash] != nil && f.queued[hash] == nil {
   331  					// Discard if already imported by other means
   332  					if f.getBlock(hash) == nil {
   333  						explicit = append(explicit, block)
   334  					} else {
   335  						f.forgetHash(hash)
   336  					}
   337  				} else {
   338  					download = append(download, block)
   339  				}
   340  			}
   341  
   342  			select {
   343  			case filter <- download:
   344  			case <-f.quit:
   345  				return
   346  			}
   347  			// Schedule the retrieved blocks for ordered import
   348  			for _, block := range explicit {
   349  				if announce := f.fetching[block.Hash()]; announce != nil {
   350  					f.enqueue(announce.origin, block)
   351  				}
   352  			}
   353  		}
   354  	}
   355  }
   356  
   357  // reschedule resets the specified fetch timer to the next announce timeout.
   358  func (f *Fetcher) reschedule(fetch *time.Timer) {
   359  	// Short circuit if no blocks are announced
   360  	if len(f.announced) == 0 {
   361  		return
   362  	}
   363  	// Otherwise find the earliest expiring announcement
   364  	earliest := time.Now()
   365  	for _, announces := range f.announced {
   366  		if earliest.After(announces[0].time) {
   367  			earliest = announces[0].time
   368  		}
   369  	}
   370  	fetch.Reset(arriveTimeout - time.Since(earliest))
   371  }
   372  
   373  // enqueue schedules a new future import operation, if the block to be imported
   374  // has not yet been seen.
   375  func (f *Fetcher) enqueue(peer string, block *types.Block) {
   376  	hash := block.Hash()
   377  
   378  	// Ensure the peer isn't DOSing us
   379  	count := f.queues[peer] + 1
   380  	if count > blockLimit {
   381  		glog.V(logger.Debug).Infof("Peer %s: discarded block #%d [%x], exceeded allowance (%d)", peer, block.NumberU64(), hash.Bytes()[:4], blockLimit)
   382  		return
   383  	}
   384  	// Discard any past or too distant blocks
   385  	if dist := int64(block.NumberU64()) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist {
   386  		glog.V(logger.Debug).Infof("Peer %s: discarded block #%d [%x], distance %d", peer, block.NumberU64(), hash.Bytes()[:4], dist)
   387  		discardMeter.Mark(1)
   388  		return
   389  	}
   390  	// Schedule the block for future importing
   391  	if _, ok := f.queued[hash]; !ok {
   392  		op := &inject{
   393  			origin: peer,
   394  			block:  block,
   395  		}
   396  		f.queues[peer] = count
   397  		f.queued[hash] = op
   398  		f.queue.Push(op, -float32(block.NumberU64()))
   399  
   400  		if glog.V(logger.Debug) {
   401  			glog.Infof("Peer %s: queued block #%d [%x], total %v", peer, block.NumberU64(), hash.Bytes()[:4], f.queue.Size())
   402  		}
   403  	}
   404  }
   405  
   406  // insert spawns a new goroutine to run a block insertion into the chain. If the
   407  // block's number is at the same height as the current import phase, if updates
   408  // the phase states accordingly.
   409  func (f *Fetcher) insert(peer string, block *types.Block) {
   410  	hash := block.Hash()
   411  
   412  	// Run the import on a new thread
   413  	glog.V(logger.Debug).Infof("Peer %s: importing block #%d [%x]", peer, block.NumberU64(), hash[:4])
   414  	go func() {
   415  		defer func() { f.done <- hash }()
   416  
   417  		// If the parent's unknown, abort insertion
   418  		parent := f.getBlock(block.ParentHash())
   419  		if parent == nil {
   420  			return
   421  		}
   422  		// Quickly validate the header and propagate the block if it passes
   423  		switch err := f.validateBlock(block, parent); err {
   424  		case nil:
   425  			// All ok, quickly propagate to our peers
   426  			broadcastTimer.UpdateSince(block.ReceivedAt)
   427  			go f.broadcastBlock(block, true)
   428  
   429  		case core.BlockFutureErr:
   430  			futureMeter.Mark(1)
   431  			// Weird future block, don't fail, but neither propagate
   432  
   433  		default:
   434  			// Something went very wrong, drop the peer
   435  			glog.V(logger.Debug).Infof("Peer %s: block #%d [%x] verification failed: %v", peer, block.NumberU64(), hash[:4], err)
   436  			f.dropPeer(peer)
   437  			return
   438  		}
   439  		// Run the actual import and log any issues
   440  		if _, err := f.insertChain(types.Blocks{block}); err != nil {
   441  			glog.V(logger.Warn).Infof("Peer %s: block #%d [%x] import failed: %v", peer, block.NumberU64(), hash[:4], err)
   442  			return
   443  		}
   444  		// If import succeeded, broadcast the block
   445  		announceTimer.UpdateSince(block.ReceivedAt)
   446  		go f.broadcastBlock(block, false)
   447  
   448  		// Invoke the testing hook if needed
   449  		if f.importedHook != nil {
   450  			f.importedHook(block)
   451  		}
   452  	}()
   453  }
   454  
   455  // forgetHash removes all traces of a block announcement from the fetcher's
   456  // internal state.
   457  func (f *Fetcher) forgetHash(hash common.Hash) {
   458  	// Remove all pending announces and decrement DOS counters
   459  	for _, announce := range f.announced[hash] {
   460  		f.announces[announce.origin]--
   461  		if f.announces[announce.origin] == 0 {
   462  			delete(f.announces, announce.origin)
   463  		}
   464  	}
   465  	delete(f.announced, hash)
   466  
   467  	// Remove any pending fetches and decrement the DOS counters
   468  	if announce := f.fetching[hash]; announce != nil {
   469  		f.announces[announce.origin]--
   470  		if f.announces[announce.origin] == 0 {
   471  			delete(f.announces, announce.origin)
   472  		}
   473  		delete(f.fetching, hash)
   474  	}
   475  }
   476  
   477  // forgetBlock removes all traces of a queued block frmo the fetcher's internal
   478  // state.
   479  func (f *Fetcher) forgetBlock(hash common.Hash) {
   480  	if insert := f.queued[hash]; insert != nil {
   481  		f.queues[insert.origin]--
   482  		if f.queues[insert.origin] == 0 {
   483  			delete(f.queues, insert.origin)
   484  		}
   485  		delete(f.queued, hash)
   486  	}
   487  }