func (a *addrBook) Stop() {
	if atomic.AddInt32(&a.shutdown, 1) != 1 {
		a.logger.Warning("Address manager is already in the process of " +
			"shutting down")
		return
	}
	a.logger.Info("Address manager shutting down")
	close(a.quit)
	a.wg.Wait()
	return
}
Stop gracefully shuts down the address manager by stopping the main handler.
package discovery
import (
	"bytes"
	"encoding/binary"
	"math/rand"
	"net"
	"path/filepath"
	"sync"
	"sync/atomic"
	"time"
	"github.com/btcsuite/btcd/chaincfg/chainhash"
	"github.com/libonomy/libonomy-p2p/crypto"
	"github.com/libonomy/libonomy-p2p/log"
	"github.com/libonomy/libonomy-p2p/p2p/config"
	"github.com/libonomy/libonomy-p2p/p2p/node"
	"github.com/libonomy/libonomy-p2p/p2p/p2pcrypto"
)
const (
	needAddressThreshold = 1000
	triedBucketSize = 256
	triedBucketCount = 64
	newBucketSize = 120
	newBucketCount = 1024
	triedBucketsPerGroup = 8
	newBucketsPerGroup = 64
	newBucketsPerAddress = 8
	numMissingDays = 30
	numRetries = 3
	maxFailures = 10
	minBadDays = 7
	getAddrMax = 300
	getAddrPercent = 23
)
type addrBook struct {
	logger log.Log
	mtx    sync.RWMutex
	path          string
	peersFileName string
	rand *rand.Rand
	key  [32]byte
	addrIndex map[node.ID]*KnownAddress  
	addrNew   [newBucketCount]map[node.ID]*KnownAddress
	addrTried [triedBucketCount]map[node.ID]*KnownAddress
	localAddrMtx   sync.RWMutex
	localAddresses []*node.Info
	nTried int
	nNew   int
	started  int32
	shutdown int32
	wg       sync.WaitGroup
	quit chan struct{}
}
func (a *addrBook) AddLocalAddress(addr *node.Info) {
	a.localAddrMtx.Lock()
	a.localAddresses = append(a.localAddresses, addr)
	a.localAddrMtx.Unlock()
}
func (a *addrBook) IsLocalAddress(addr *node.Info) bool {
	a.localAddrMtx.RLock()
	ok := a.isLocalAddressUnlocked(addr)
	a.localAddrMtx.RUnlock()
	return ok
}
func (a *addrBook) isLocalAddressUnlocked(addr *node.Info) bool {
	for _, local := range a.localAddresses {
		if bytes.Equal(local.ID.Bytes(), addr.ID.Bytes()) {
			return true
		}
		if local.ProtocolPort != 0 && local.DiscoveryPort != 0 {
			if local.IP.Equal(addr.IP) && (local.ProtocolPort == addr.ProtocolPort && local.DiscoveryPort == addr.DiscoveryPort) {
				return true
			}
		}
	}
	return false
}
func (a *addrBook) updateAddress(netAddr, srcAddr *node.Info) {
	if a.IsLocalAddress(netAddr) {
		a.logger.Debug("skipping adding a local address %v", netAddr.String())
		return
	}
	if !IsRoutable(netAddr.IP) && IsRoutable(srcAddr.IP) {
		a.logger.Debug("skipping adding non routable address%v", netAddr.String())
		return
	}
	ka := a.lookup(netAddr.PublicKey())
	if ka != nil {
		ka.lastSeen = time.Now()
		if ka.tried {
			return
		}
		if ka.refs == newBucketsPerAddress {
			return
		}
		return
	}
	ka = &KnownAddress{na: netAddr, srcAddr: srcAddr, lastSeen: time.Now()}
	a.addrIndex[netAddr.ID] = ka
	a.nNew++
	bucket := a.getNewBucket(netAddr.IP, srcAddr.IP)
	if _, ok := a.addrNew[bucket][netAddr.ID]; ok {
		return
	}
	if len(a.addrNew[bucket]) > newBucketSize {
		a.logger.Debug("new bucket is full, expiring old")
		a.expireNew(bucket)
	}
	ka.refs++
	a.addrNew[bucket][netAddr.ID] = ka
	a.logger.Debug("Added new address %s for a total of %d addresses", netAddr.String(), a.nTried+a.nNew)
}
func (a *addrBook) GetAddress() *KnownAddress {
	a.mtx.Lock()
	defer a.mtx.Unlock()
	if a.numAddresses() == 0 {
		return nil
	}
	if a.nTried > 0 && (a.nNew == 0 || a.rand.Intn(2) == 0) {
		large := 1 << 30
		factor := 1.0
		for {
			bucket := a.rand.Intn(len(a.addrTried))
			if len(a.addrTried[bucket]) == 0 {
				continue
			}
			var ka *KnownAddress
			nth := a.rand.Intn(len(a.addrTried[bucket]))
			for _, value := range a.addrTried[bucket] {
				if nth == 0 {
					ka = value
				}
				nth--
			}
			randval := a.rand.Intn(large)
			if float64(randval) < (factor * ka.chance() * float64(large)) {
				a.logger.Debug("Selected %v from tried bucket", ka.na.String())
				return ka
			}
			factor *= 1.2
		}
	} else {
		large := 1 << 30
		factor := 1.0
		for {
			bucket := a.rand.Intn(len(a.addrNew))
			if len(a.addrNew[bucket]) == 0 {
				continue
			}
			var ka *KnownAddress
			nth := a.rand.Intn(len(a.addrNew[bucket]))
			for _, value := range a.addrNew[bucket] {
				if nth == 0 {
					ka = value
				}
				nth--
			}
			randval := a.rand.Intn(large)
			if float64(randval) < (factor * ka.chance() * float64(large)) {
				a.logger.Debug("Selected %v from new bucket", ka.na.String())
				return ka
			}
			factor *= 1.2
		}
	}
}
func (a *addrBook) Lookup(addr p2pcrypto.PublicKey) (*node.Info, error) {
	a.mtx.Lock()
	d := a.lookup(addr)
	a.mtx.Unlock()
	if d == nil {
		return nil, ErrLookupFailed
	}
	return d.na, nil
}
func (a *addrBook) lookup(addr p2pcrypto.PublicKey) *KnownAddress {
	return a.addrIndex[addr.Array()]
}
func (a *addrBook) Attempt(key p2pcrypto.PublicKey) {
	a.mtx.Lock()
	defer a.mtx.Unlock()
	ka := a.lookup(key)
	if ka == nil {
		return
	}
	ka.attempts++
	ka.lastattempt = time.Now()
	a.moveToTriedUnlocked(ka)
}
func (a *addrBook) Good(addr p2pcrypto.PublicKey) {
	a.mtx.Lock()
	defer a.mtx.Unlock()
	ka := a.lookup(addr)
	if ka == nil {
		return
	}
	now := time.Now()
	ka.lastsuccess = now
	ka.lastattempt = now
	ka.lastSeen = now
	ka.attempts = 0
	a.moveToTriedUnlocked(ka)
}
func (a *addrBook) moveToTriedUnlocked(ka *KnownAddress) {
	if ka.tried {
		return
	}
	addr := ka.na.PublicKey()
	addrKey := addr.Array()
	oldBucket := -1
	for i := range a.addrNew {
		if _, ok := a.addrNew[i][addrKey]; ok {
			delete(a.addrNew[i], addrKey)
			ka.refs--
			if oldBucket == -1 {
				oldBucket = i
			}
		}
	}
	if oldBucket == -1 {
		return
	}
	a.nNew--
	bucket := a.getTriedBucket(ka.na.IP)
	if len(a.addrTried[bucket]) < triedBucketSize {
		ka.tried = true
		a.addrTried[bucket][addr.Array()] = ka
		a.nTried++
		return
	}
	rmka := a.pickTried(bucket)
	newBucket := a.getNewBucket(rmka.na.IP, rmka.srcAddr.IP)
	if len(a.addrNew[newBucket]) >= newBucketSize {
		newBucket = oldBucket
	}
	ka.tried = true
	a.addrTried[bucket][ka.na.ID] = ka
	rmka.tried = false
	rmka.refs++
	a.nNew++
	rmkey := rmka.na.ID
	a.logger.Debug("Replacing %s with %s in tried", rmkey, addrKey)
	a.addrNew[newBucket][rmkey] = rmka
}
func (a *addrBook) pickTried(bucket int) *KnownAddress {
	var oldest *KnownAddress
	for _, ka := range a.addrTried[bucket] {
		if oldest == nil || oldest.lastSeen.After(ka.lastSeen) {
			oldest = ka
		}
	}
	return oldest
}
func (a *addrBook) numAddresses() int {
	return a.nTried + a.nNew
}
func (a *addrBook) NumAddresses() int {
	a.mtx.Lock()
	defer a.mtx.Unlock()
	return a.numAddresses()
}
func (a *addrBook) NeedNewAddresses() bool {
	a.mtx.Lock()
	if a.nNew < a.nTried && a.nNew < needAddressThreshold/2 {
		a.mtx.Unlock()
		return true
	}
	a.mtx.Unlock()
	return false
}
func (a *addrBook) AddressCache() []*node.Info {
	allAddr := a.getAddresses()
	numAddresses := len(allAddr) * getAddrPercent / 100
	if numAddresses > getAddrMax {
		numAddresses = getAddrMax
	} else if numAddresses == 0 {
		numAddresses = len(allAddr)
	}
	for i := 0; i < numAddresses; i++ {
		j := rand.Intn(len(allAddr)-i) + i
		allAddr[i], allAddr[j] = allAddr[j], allAddr[i]
	}
	return allAddr[0:numAddresses]
}
func (a *addrBook) getAddresses() []*node.Info {
	a.mtx.Lock()
	defer a.mtx.Unlock()
	addrIndexLen := len(a.addrIndex)
	if addrIndexLen == 0 {
		return nil
	}
	addrs := make([]*node.Info, 0, addrIndexLen)
	for _, v := range a.addrIndex {
		addrs = append(addrs, v.na)
	}
	return addrs
}
func (a *addrBook) Start() {
	if atomic.AddInt32(&a.started, 1) != 1 {
		return
	}
	if a.path != "" {
		peersFileName := defaultPeersFileName
		if a.peersFileName != "" {
			peersFileName = a.peersFileName
		}
		finalFilePath := filepath.Join(a.path, config.P2PDirectoryPath, peersFileName)
		a.loadPeers(finalFilePath)
		a.wg.Add(1)
		go func() { a.saveRoutine(finalFilePath); a.wg.Done() }()
	}
}