func (sm *SyncManager) handleDonePeerMsg(peer *peerpkg.Peer) {
	state, exists := sm.peerStates[peer]
	if !exists {
		log.Warnf("Received done peer message for unknown peer %s", peer)
		return
	}
	delete(sm.peerStates, peer)
	log.Infof("Lost peer %s", peer)
	sm.clearRequestedState(state)
	if peer == sm.syncPeer {
		sm.updateSyncPeer(false)
	}
}
handleDonePeerMsg deals with peers that have signalled they are done.  It
removes the peer as a candidate for syncing and in the case where it was
the current sync peer, attempts to select a new best peer to sync from.  It
is invoked from the syncHandler goroutine.
package netsync
import (
	"container/list"
	"math/rand"
	"net"
	"sync"
	"sync/atomic"
	"time"
	"github.com/soteria-dag/soterd/blockdag"
	"github.com/soteria-dag/soterd/chaincfg"
	"github.com/soteria-dag/soterd/chaincfg/chainhash"
	"github.com/soteria-dag/soterd/database"
	"github.com/soteria-dag/soterd/mempool"
	peerpkg "github.com/soteria-dag/soterd/peer"
	"github.com/soteria-dag/soterd/soterutil"
	"github.com/soteria-dag/soterd/wire"
)
const (
	minInFlightBlocks = 10
	maxRejectedTxns = 1000
	maxRequestedBlocks = wire.MaxInvPerMsg
	maxRequestedTxns = wire.MaxInvPerMsg
	maxStallDuration = 3 * time.Minute
	stallSampleInterval = 30 * time.Second
)
var zeroHash chainhash.Hash
type newPeerMsg struct {
	peer *peerpkg.Peer
}
type blockMsg struct {
	block *soterutil.Block
	peer  *peerpkg.Peer
	reply chan struct{}
}
type invMsg struct {
	inv  *wire.MsgInv
	peer *peerpkg.Peer
}
type headersMsg struct {
	headers *wire.MsgHeaders
	peer    *peerpkg.Peer
}
type donePeerMsg struct {
	peer *peerpkg.Peer
}
type txMsg struct {
	tx    *soterutil.Tx
	peer  *peerpkg.Peer
	reply chan struct{}
}
type getSyncPeerMsg struct {
	reply chan int32
}
type processBlockResponse struct {
	isOrphan bool
	err      error
}
type processBlockMsg struct {
	block *soterutil.Block
	flags blockdag.BehaviorFlags
	reply chan processBlockResponse
}
type isCurrentMsg struct {
	reply chan bool
}
type pauseMsg struct {
	unpause <-chan struct{}
}
type headerNode struct {
	height int32
	hash   *chainhash.Hash
}
type peerSyncState struct {
	syncCandidate   bool
	requestQueue    []*wire.InvVect
	requestedTxns   map[chainhash.Hash]*requestExpiry
	requestedBlocks map[chainhash.Hash]*requestExpiry
}
type requestExpiry struct {
	reqTime time.Time
	attempts int
}
type SyncManager struct {
	peerNotifier   PeerNotifier
	started        int32
	shutdown       int32
	chain          *blockdag.BlockDAG
	txMemPool      *mempool.TxPool
	chainParams    *chaincfg.Params
	progressLogger *blockProgressLogger
	msgChan        chan interface{}
	wg             sync.WaitGroup
	quit           chan struct{}
	rejectedTxns    map[chainhash.Hash]struct{}
	requestedTxns   map[chainhash.Hash]*requestExpiry
	requestedBlocks map[chainhash.Hash]*requestExpiry
	syncPeer        *peerpkg.Peer
	peerStates      map[*peerpkg.Peer]*peerSyncState
	lastProgressTime time.Time
	headersFirstMode bool
	headerList       *list.List
	startHeader      *list.Element
	feeEstimator *mempool.FeeEstimator
}
func (e *requestExpiry) expired() bool {
	return time.Now().After(e.reqTime.Add(stallSampleInterval))
}
func (e *requestExpiry) reset() {
	e.reqTime = time.Now()
	e.attempts = 0
}
func NewRequestExpiry() *requestExpiry {
	return &requestExpiry{
		reqTime: time.Now(),
	}
}
func (sm *SyncManager) canReqBlock(state *peerSyncState, hash chainhash.Hash) bool {
	peerReqExp, peerExists := state.requestedBlocks[hash]
	if peerExists && !peerReqExp.expired() {
		return false
	}
	reqExp, exists := sm.requestedBlocks[hash]
	if exists && !reqExp.expired() {
		return false
	}
	return true
}
func (sm *SyncManager) canReqTxn(state *peerSyncState, hash chainhash.Hash) bool {
	peerReqExp, peerExists := state.requestedTxns[hash]
	if peerExists && !peerReqExp.expired() {
		return false
	}
	reqExp, exists := sm.requestedTxns[hash]
	if exists && !reqExp.expired() {
		return false
	}
	return true
}
func (sm *SyncManager) hasPendingBlocks(peer *peerpkg.Peer) bool {
	state, exists := sm.peerStates[peer]
	if !exists {
		return false
	}
	for _, req := range state.requestedBlocks {
		if !req.expired() {
			return true
		}
	}
	return false
}
func (sm *SyncManager) resetHeaderState(newestHash *chainhash.Hash, newestHeight int32) {
	sm.headersFirstMode = false
	sm.headerList.Init()
	sm.startHeader = nil
}
func (sm *SyncManager) startSync() {
	if sm.syncPeer != nil {
		return
	}
	segwitActive, err := sm.chain.IsDeploymentActive(chaincfg.DeploymentSegwit)
	if err != nil {
		log.Errorf("Unable to query for segwit soft-fork state: %v", err)
		return
	}
	dagState := sm.chain.DAGSnapshot()
	var equalPeers, higherPeers []*peerpkg.Peer
	for peer, state := range sm.peerStates {
		if !state.syncCandidate {
			continue
		}
		if segwitActive && !peer.IsWitnessEnabled() {
			log.Debugf("peer %v not witness enabled, skipping", peer)
			continue
		}
		if peer.MaxBlockHeight() < dagState.MaxHeight {
			state.syncCandidate = false
			continue
		}
		if peer.MaxBlockHeight() > dagState.MaxHeight {
			higherPeers = append(higherPeers, peer)
		} else if peer.MaxBlockHeight() == dagState.MaxHeight {
			equalPeers = append(equalPeers, peer)
		}
	}
	var bestPeer *peerpkg.Peer
	switch {
	case len(higherPeers) > 0:
		bestPeer = higherPeers[rand.Intn(len(higherPeers))]
	case len(equalPeers) > 0:
		bestPeer = equalPeers[rand.Intn(len(equalPeers))]
	}
	if bestPeer != nil {
		sm.requestedBlocks = make(map[chainhash.Hash]*requestExpiry)
		locator, err := sm.chain.LatestBlockLocator()
		if err != nil {
			log.Errorf("Failed to get block locator for the "+
				"latest block: %v", err)
			return
		}
		log.Infof("Syncing to block height %d from peer %v",
			bestPeer.MaxBlockHeight(), bestPeer.Addr())
		_ = bestPeer.PushGetBlocksMsg(locator, &zeroHash)
		sm.syncPeer = bestPeer
		sm.lastProgressTime = time.Now()
	} else {
		log.Warnf("No sync peer candidates available")
	}
}
func (sm *SyncManager) stopSync() {
	log.Debugf("Stopping sync with peer %v", sm.syncPeer.Addr())
	sm.syncPeer = nil
	if sm.headersFirstMode {
		best := sm.chain.BestSnapshot()
		sm.resetHeaderState(&best.Hash, best.Height)
	}
}
func (sm *SyncManager) isSyncCandidate(peer *peerpkg.Peer) bool {
	if sm.chainParams == &chaincfg.RegressionNetParams {
		host, _, err := net.SplitHostPort(peer.Addr())
		if err != nil {
			return false
		}
		if host != "127.0.0.1" && host != "localhost" {
			return false
		}
	} else {
		segwitActive, err := sm.chain.IsDeploymentActive(chaincfg.DeploymentSegwit)
		if err != nil {
			log.Errorf("Unable to query for segwit "+
				"soft-fork state: %v", err)
		}
		nodeServices := peer.Services()
		if nodeServices&wire.SFNodeNetwork != wire.SFNodeNetwork ||
			(segwitActive && !peer.IsWitnessEnabled()) {
			return false
		}
	}
	return true
}
func (sm *SyncManager) handleNewPeerMsg(peer *peerpkg.Peer) {
	if atomic.LoadInt32(&sm.shutdown) != 0 {
		return
	}
	log.Infof("New valid peer %s (%s)", peer, peer.UserAgent())
	isSyncCandidate := sm.isSyncCandidate(peer)
	sm.peerStates[peer] = &peerSyncState{
		syncCandidate:   isSyncCandidate,
		requestedTxns:   make(map[chainhash.Hash]*requestExpiry),
		requestedBlocks: make(map[chainhash.Hash]*requestExpiry),
	}
	if isSyncCandidate && sm.syncPeer == nil {
		sm.startSync()
	}
}
func (sm *SyncManager) handleStallSample() {
	if atomic.LoadInt32(&sm.shutdown) != 0 {
		return
	}
	if sm.syncPeer == nil {
		return
	}
	if time.Since(sm.lastProgressTime) <= maxStallDuration {
		return
	}
	state, exists := sm.peerStates[sm.syncPeer]
	if !exists {
		return
	}
	sm.clearRequestedState(state)
	disconnectSyncPeer := sm.shouldDCStalledSyncPeer()
	sm.updateSyncPeer(disconnectSyncPeer)
}
func (sm *SyncManager) shouldDCStalledSyncPeer() bool {
	maxHeight := sm.syncPeer.MaxBlockHeight()
	startHeight := sm.syncPeer.StartingHeight()
	var peerHeight int32
	if maxHeight > startHeight {
		peerHeight = maxHeight
	} else {
		peerHeight = startHeight
	}
	best := sm.chain.BestSnapshot()
	return peerHeight > best.Height
}