code: func (sm *SyncManager) fetchHeaderBlocks() {
  	// Nothing to do if there is no start header.
  	if sm.startHeader == nil {
  		log.Warnf("fetchHeaderBlocks called with no start header")
  		return
  	}
  
  	// Build up a getdata request for the list of blocks the headers
  	// describe.  The size hint will be limited to wire.MaxInvPerMsg by
  	// the function, so no need to double check it here.
  	gdmsg := wire.NewMsgGetDataSizeHint(uint(sm.headerList.Len()))
  	numRequested := 0
  	for e := sm.startHeader; e != nil; e = e.Next() {
  		node, ok := e.Value.(*headerNode)
  		if !ok {
  			log.Warn("Header list node type is not a headerNode")
  			continue
  		}
  
  		iv := wire.NewInvVect(wire.InvTypeBlock, node.hash)
  		haveInv, err := sm.haveInventory(iv)
  		if err != nil {
  			log.Warnf("Unexpected failure when checking for "+
  				"existing inventory during header block "+
  				"fetch: %v", err)
  		}
  		if !haveInv {
  			syncPeerState := sm.peerStates[sm.syncPeer]
  
  			sm.requestedBlocks[*node.hash] = struct{}{}
  			syncPeerState.requestedBlocks[*node.hash] = struct{}{}
  
  			// If we're fetching from a witness enabled peer
  			// post-fork, then ensure that we receive all the
  			// witness data in the blocks.
  			if sm.syncPeer.IsWitnessEnabled() {
  				iv.Type = wire.InvTypeWitnessBlock
  			}
  
  			gdmsg.AddInvVect(iv)
  			numRequested++
  		}
  		sm.startHeader = e.Next()
  		if numRequested >= wire.MaxInvPerMsg {
  			break
  		}
  	}
  	if len(gdmsg.InvList) > 0 {
  		sm.syncPeer.QueueMessage(gdmsg, nil)
  	}
  }
docs: // fetchHeaderBlocks creates and sends a request to the syncPeer for the next
  // list of blocks to be downloaded based on the current list of headers.