func (a *addrBook) addToOldBucket(ka *knownAddress, bucketIdx int) bool {
	if ka.isNew() {
		a.Logger.Error(cmn.Fmt("Cannot add new address to old bucket: %v", ka))
		return false
	}
	if len(ka.Buckets) != 0 {
		a.Logger.Error(cmn.Fmt("Cannot add already old address to another old bucket: %v", ka))
		return false
	}
	addrStr := ka.Addr.String()
	bucket := a.getBucket(bucketTypeOld, bucketIdx)
	if _, ok := bucket[addrStr]; ok {
		return true
	}
	if len(bucket) > oldBucketSize {
		return false
	}
	bucket[addrStr] = ka
	if ka.addBucketRef(bucketIdx) == 1 {
		a.nOld++
	}
	a.addrLookup[ka.ID()] = ka
	return true
}
Adds ka to old bucket. Returns false if it couldn't do it cuz buckets full.
package pex
import (
	"crypto/sha256"
	"encoding/binary"
	"math"
	"net"
	"sync"
	"time"
	crypto "github.com/tendermint/tendermint/crypto"
	cmn "github.com/tendermint/tendermint/libs/common"
	"github.com/tendermint/tendermint/p2p"
)
const (
	bucketTypeNew = 0x01
	bucketTypeOld = 0x02
)
type AddrBook interface {
	cmn.Service
	AddOurAddress(*p2p.NetAddress)
	OurAddress(*p2p.NetAddress) bool
	AddPrivateIDs([]string)
	AddAddress(addr *p2p.NetAddress, src *p2p.NetAddress) error
	RemoveAddress(*p2p.NetAddress)
	HasAddress(*p2p.NetAddress) bool
	NeedMoreAddrs() bool
	PickAddress(biasTowardsNewAddrs int) *p2p.NetAddress
	MarkGood(*p2p.NetAddress)
	MarkAttempt(*p2p.NetAddress)
	MarkBad(*p2p.NetAddress)
	IsGood(*p2p.NetAddress) bool
	GetSelection() []*p2p.NetAddress
	GetSelectionWithBias(biasTowardsNewAddrs int) []*p2p.NetAddress
	ListOfKnownAddresses() []*knownAddress
	Save()
}
var _ AddrBook = (*addrBook)(nil)
type addrBook struct {
	cmn.BaseService
	filePath          string
	routabilityStrict bool
	key               string  
	mtx        sync.Mutex
	rand       *cmn.Rand
	ourAddrs   map[string]struct{}
	privateIDs map[p2p.ID]struct{}
	addrLookup map[p2p.ID]*knownAddress  
	bucketsOld []map[string]*knownAddress
	bucketsNew []map[string]*knownAddress
	nOld       int
	nNew       int
	wg sync.WaitGroup
}
func NewAddrBook(filePath string, routabilityStrict bool) *addrBook {
	am := &addrBook{
		rand:              cmn.NewRand(),
		ourAddrs:          make(map[string]struct{}),
		privateIDs:        make(map[p2p.ID]struct{}),
		addrLookup:        make(map[p2p.ID]*knownAddress),
		filePath:          filePath,
		routabilityStrict: routabilityStrict,
	}
	am.init()
	am.BaseService = *cmn.NewBaseService(nil, "AddrBook", am)
	return am
}
func (a *addrBook) init() {
	a.key = crypto.CRandHex(24)  
	a.bucketsNew = make([]map[string]*knownAddress, newBucketCount)
	for i := range a.bucketsNew {
		a.bucketsNew[i] = make(map[string]*knownAddress)
	}
	a.bucketsOld = make([]map[string]*knownAddress, oldBucketCount)
	for i := range a.bucketsOld {
		a.bucketsOld[i] = make(map[string]*knownAddress)
	}
}
func (a *addrBook) OnStart() error {
	if err := a.BaseService.OnStart(); err != nil {
		return err
	}
	a.loadFromFile(a.filePath)
	a.wg.Add(1)
	go a.saveRoutine()
	return nil
}
func (a *addrBook) OnStop() {
	a.BaseService.OnStop()
}
func (a *addrBook) Wait() {
	a.wg.Wait()
}
func (a *addrBook) FilePath() string {
	return a.filePath
}
func (a *addrBook) AddOurAddress(addr *p2p.NetAddress) {
	a.mtx.Lock()
	defer a.mtx.Unlock()
	a.Logger.Info("Add our address to book", "addr", addr)
	a.ourAddrs[addr.String()] = struct{}{}
}
func (a *addrBook) OurAddress(addr *p2p.NetAddress) bool {
	a.mtx.Lock()
	_, ok := a.ourAddrs[addr.String()]
	a.mtx.Unlock()
	return ok
}
func (a *addrBook) AddPrivateIDs(IDs []string) {
	a.mtx.Lock()
	defer a.mtx.Unlock()
	for _, id := range IDs {
		a.privateIDs[p2p.ID(id)] = struct{}{}
	}
}
func (a *addrBook) AddAddress(addr *p2p.NetAddress, src *p2p.NetAddress) error {
	a.mtx.Lock()
	defer a.mtx.Unlock()
	return a.addAddress(addr, src)
}
func (a *addrBook) RemoveAddress(addr *p2p.NetAddress) {
	a.mtx.Lock()
	defer a.mtx.Unlock()
	ka := a.addrLookup[addr.ID]
	if ka == nil {
		return
	}
	a.Logger.Info("Remove address from book", "addr", ka.Addr, "ID", ka.ID())
	a.removeFromAllBuckets(ka)
}
func (a *addrBook) IsGood(addr *p2p.NetAddress) bool {
	a.mtx.Lock()
	defer a.mtx.Unlock()
	return a.addrLookup[addr.ID].isOld()
}
func (a *addrBook) HasAddress(addr *p2p.NetAddress) bool {
	a.mtx.Lock()
	defer a.mtx.Unlock()
	ka := a.addrLookup[addr.ID]
	return ka != nil
}
func (a *addrBook) NeedMoreAddrs() bool {
	return a.Size() < needAddressThreshold
}
func (a *addrBook) PickAddress(biasTowardsNewAddrs int) *p2p.NetAddress {
	a.mtx.Lock()
	defer a.mtx.Unlock()
	bookSize := a.size()
	if bookSize <= 0 {
		if bookSize < 0 {
			a.Logger.Error("Addrbook size less than 0", "nNew", a.nNew, "nOld", a.nOld)
		}
		return nil
	}
	if biasTowardsNewAddrs > 100 {
		biasTowardsNewAddrs = 100
	}
	if biasTowardsNewAddrs < 0 {
		biasTowardsNewAddrs = 0
	}
	oldCorrelation := math.Sqrt(float64(a.nOld)) * (100.0 - float64(biasTowardsNewAddrs))
	newCorrelation := math.Sqrt(float64(a.nNew)) * float64(biasTowardsNewAddrs)
	var bucket map[string]*knownAddress
	pickFromOldBucket := (newCorrelation+oldCorrelation)*a.rand.Float64() < oldCorrelation
	if (pickFromOldBucket && a.nOld == 0) ||
		(!pickFromOldBucket && a.nNew == 0) {
		return nil
	}
	for len(bucket) == 0 {
		if pickFromOldBucket {
			bucket = a.bucketsOld[a.rand.Intn(len(a.bucketsOld))]
		} else {
			bucket = a.bucketsNew[a.rand.Intn(len(a.bucketsNew))]
		}
	}
	randIndex := a.rand.Intn(len(bucket))
	for _, ka := range bucket {
		if randIndex == 0 {
			return ka.Addr
		}
		randIndex--
	}
	return nil
}
func (a *addrBook) MarkGood(addr *p2p.NetAddress) {
	a.mtx.Lock()
	defer a.mtx.Unlock()
	ka := a.addrLookup[addr.ID]
	if ka == nil {
		return
	}
	ka.markGood()
	if ka.isNew() {
		a.moveToOld(ka)
	}
}
func (a *addrBook) MarkAttempt(addr *p2p.NetAddress) {
	a.mtx.Lock()
	defer a.mtx.Unlock()
	ka := a.addrLookup[addr.ID]
	if ka == nil {
		return
	}
	ka.markAttempt()
}
func (a *addrBook) MarkBad(addr *p2p.NetAddress) {
	a.RemoveAddress(addr)
}
func (a *addrBook) GetSelection() []*p2p.NetAddress {
	a.mtx.Lock()
	defer a.mtx.Unlock()
	bookSize := a.size()
	if bookSize <= 0 {
		if bookSize < 0 {
			a.Logger.Error("Addrbook size less than 0", "nNew", a.nNew, "nOld", a.nOld)
		}
		return nil
	}
	numAddresses := cmn.MaxInt(
		cmn.MinInt(minGetSelection, bookSize),
		bookSize*getSelectionPercent/100)
	numAddresses = cmn.MinInt(maxGetSelection, numAddresses)
	allAddr := make([]*p2p.NetAddress, bookSize)
	i := 0
	for _, ka := range a.addrLookup {
		allAddr[i] = ka.Addr
		i++
	}
	for i := 0; i < numAddresses; i++ {
		j := cmn.RandIntn(len(allAddr)-i) + i
		allAddr[i], allAddr[j] = allAddr[j], allAddr[i]
	}
	return allAddr[:numAddresses]
}
func (a *addrBook) GetSelectionWithBias(biasTowardsNewAddrs int) []*p2p.NetAddress {
	a.mtx.Lock()
	defer a.mtx.Unlock()
	bookSize := a.size()
	if bookSize <= 0 {
		if bookSize < 0 {
			a.Logger.Error("Addrbook size less than 0", "nNew", a.nNew, "nOld", a.nOld)
		}
		return nil
	}
	if biasTowardsNewAddrs > 100 {
		biasTowardsNewAddrs = 100
	}
	if biasTowardsNewAddrs < 0 {
		biasTowardsNewAddrs = 0
	}
	numAddresses := cmn.MaxInt(
		cmn.MinInt(minGetSelection, bookSize),
		bookSize*getSelectionPercent/100)
	numAddresses = cmn.MinInt(maxGetSelection, numAddresses)
	selection := make([]*p2p.NetAddress, numAddresses)
	oldBucketToAddrsMap := make(map[int]map[string]struct{})
	var oldIndex int
	newBucketToAddrsMap := make(map[int]map[string]struct{})
	var newIndex int
	selectionIndex := 0
ADDRS_LOOP:
	for selectionIndex < numAddresses {
		pickFromOldBucket := int((float64(selectionIndex)/float64(numAddresses))*100) >= biasTowardsNewAddrs
		pickFromOldBucket = (pickFromOldBucket && a.nOld > 0) || a.nNew == 0
		bucket := make(map[string]*knownAddress)
		for len(bucket) == 0 {
			if pickFromOldBucket {
				oldIndex = a.rand.Intn(len(a.bucketsOld))
				bucket = a.bucketsOld[oldIndex]
			} else {
				newIndex = a.rand.Intn(len(a.bucketsNew))
				bucket = a.bucketsNew[newIndex]
			}
		}
		randIndex := a.rand.Intn(len(bucket))
		var selectedAddr *p2p.NetAddress
		for _, ka := range bucket {
			if randIndex == 0 {
				selectedAddr = ka.Addr
				break
			}
			randIndex--
		}
		if pickFromOldBucket {
			if addrsMap, ok := oldBucketToAddrsMap[oldIndex]; ok {
				if _, ok = addrsMap[selectedAddr.String()]; ok {
					continue ADDRS_LOOP
				}
			} else {
				oldBucketToAddrsMap[oldIndex] = make(map[string]struct{})
			}
			oldBucketToAddrsMap[oldIndex][selectedAddr.String()] = struct{}{}
		} else {
			if addrsMap, ok := newBucketToAddrsMap[newIndex]; ok {
				if _, ok = addrsMap[selectedAddr.String()]; ok {
					continue ADDRS_LOOP
				}
			} else {
				newBucketToAddrsMap[newIndex] = make(map[string]struct{})
			}
			newBucketToAddrsMap[newIndex][selectedAddr.String()] = struct{}{}
		}
		selection[selectionIndex] = selectedAddr
		selectionIndex++
	}
	return selection
}
func (a *addrBook) ListOfKnownAddresses() []*knownAddress {
	a.mtx.Lock()
	defer a.mtx.Unlock()
	addrs := []*knownAddress{}
	for _, addr := range a.addrLookup {
		addrs = append(addrs, addr.copy())
	}
	return addrs
}
func (a *addrBook) Size() int {
	a.mtx.Lock()
	defer a.mtx.Unlock()
	return a.size()
}
func (a *addrBook) size() int {
	return a.nNew + a.nOld
}
func (a *addrBook) Save() {
	a.saveToFile(a.filePath)  
}
func (a *addrBook) saveRoutine() {
	defer a.wg.Done()
	saveFileTicker := time.NewTicker(dumpAddressInterval)
out:
	for {
		select {
		case <-saveFileTicker.C:
			a.saveToFile(a.filePath)
		case <-a.Quit():
			break out
		}
	}
	saveFileTicker.Stop()
	a.saveToFile(a.filePath)
	a.Logger.Info("Address handler done")
}
func (a *addrBook) getBucket(bucketType byte, bucketIdx int) map[string]*knownAddress {
	switch bucketType {
	case bucketTypeNew:
		return a.bucketsNew[bucketIdx]
	case bucketTypeOld:
		return a.bucketsOld[bucketIdx]
	default:
		cmn.PanicSanity("Should not happen")
		return nil
	}
}
func (a *addrBook) addToNewBucket(ka *knownAddress, bucketIdx int) {
	if ka.isOld() {
		a.Logger.Error("Failed Sanity Check! Cant add old address to new bucket", "ka", ka, "bucket", bucketIdx)
		return
	}
	addrStr := ka.Addr.String()
	bucket := a.getBucket(bucketTypeNew, bucketIdx)
	if _, ok := bucket[addrStr]; ok {
		return
	}
	if len(bucket) > newBucketSize {
		a.Logger.Info("new bucket is full, expiring new")
		a.expireNew(bucketIdx)
	}
	bucket[addrStr] = ka
	if ka.addBucketRef(bucketIdx) == 1 {
		a.nNew++
	}
	a.addrLookup[ka.ID()] = ka
}